Created
November 26, 2017 04:23
-
-
Save anandabits/3760cc89f3ed4cba2a35c4d150c5bf57 to your computer and use it in GitHub Desktop.
Emulating GADT in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
If you're willing to write a little bit of boilerplate you can have type-safe GADT in Swift today. | |
This is accomplished using a wrapper struct with a phantom type parameter that wraps a private enum value. | |
Static factory methods on the struct wrap each case returning a value with the phantom type bound as necessary. | |
An extension is created for each phantom type binding providng a `switch` method that requires each case | |
with a matching type to be covered and uses `fatalError` for cases where values will never be created. | |
New members are added in extensions that bind the phantom type and make use of the `switch` method. | |
The example below is drawn from https://en.m.wikibooks.org/wiki/Haskell/GADT | |
*/ | |
struct Expr<T> { | |
// The private enum and value are only used in the case factory and switch methods | |
// that make up the foundational implementation of the type itself. | |
private enum _Expr { | |
case int(Int) | |
case bool(Bool) | |
indirect case add(Expr<Int>, Expr<Int>) | |
indirect case mul(Expr<Int>, Expr<Int>) | |
indirect case eq(Expr<Int>, Expr<Int>) | |
} | |
private var _expr: _Expr | |
// Case factory methods bind the phantom type | |
static func int(_ value: Int) -> Expr<Int> { | |
return Expr<Int>(_expr: .int(value)) | |
} | |
static func bool(_ value: Bool) -> Expr<Bool> { | |
return Expr<Bool>(_expr: .bool(value)) | |
} | |
static func add(_ lhs: Expr<Int>, _ rhs: Expr<Int>) -> Expr<Int> { | |
return Expr<Int>(_expr: .add(lhs, rhs)) | |
} | |
static func mul(_ lhs: Expr<Int>, _ rhs: Expr<Int>) -> Expr<Int> { | |
return Expr<Int>(_expr: .mul(lhs, rhs)) | |
} | |
static func eq(_ lhs: Expr<Int>, _ rhs: Expr<Int>) -> Expr<Bool> { | |
return Expr<Bool>(_expr: .eq(lhs, rhs)) | |
} | |
} | |
// Pattern matching is faciliated by allowing users to switch over cases that may be constructed | |
// with the phantom type bound to a specific type. | |
extension Expr where T == Bool { | |
func `switch`<T>( | |
bool: (Bool) -> T, | |
eq: (Expr<Int>, Expr<Int>) -> T | |
) -> T { | |
switch _expr { | |
case .int, .add, .mul: | |
fatalError() | |
case .bool(let value): | |
return bool(value) | |
case .eq(let lhs, let rhs): | |
return eq(lhs, rhs) | |
} | |
} | |
} | |
extension Expr where T == Int { | |
func `switch`<T>( | |
int: (Int) -> T, | |
add: (Expr<Int>, Expr<Int>) -> T, | |
mul: (Expr<Int>, Expr<Int>) -> T | |
) -> T { | |
switch _expr { | |
case .bool, .eq: | |
fatalError() | |
case .int(let value): | |
return int(value) | |
case .add(let lhs, let rhs): | |
return add(lhs, rhs) | |
case .mul(let lhs, let rhs): | |
return mul(lhs, rhs) | |
} | |
} | |
} | |
// Values of the type are used by switching over values in extensions where the phantom type is bound. | |
extension Expr where T == Bool { | |
var value: Bool { | |
return `switch`( | |
bool: { $0 }, | |
eq: { $0.value == $1.value } | |
) | |
} | |
} | |
extension Expr where T == Int { | |
var value: Int { | |
return `switch`( | |
int: { $0 }, | |
add: { $0.value + $1.value }, | |
mul: { $0.value * $1.value } | |
) | |
} | |
} | |
// Values are created using exactly the same syntax used by enum cases themselves | |
let expr = Expr<Bool>.eq(.int(42), .add(.int(24), .int(18))) | |
let val = expr.value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment