Skip to content

Instantly share code, notes, and snippets.

@anandabits
Created November 26, 2017 04:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anandabits/3760cc89f3ed4cba2a35c4d150c5bf57 to your computer and use it in GitHub Desktop.
Save anandabits/3760cc89f3ed4cba2a35c4d150c5bf57 to your computer and use it in GitHub Desktop.
Emulating GADT in Swift
/*
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