Skip to content

Instantly share code, notes, and snippets.

@inamiy
Created March 7, 2021 08:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save inamiy/2edb7b8b7e7b5f18b7fbc7e0199c2508 to your computer and use it in GitHub Desktop.
Save inamiy/2edb7b8b7e7b5f18b7fbc7e0199c2508 to your computer and use it in GitHub Desktop.
Mimicking extensibles (intersection/union types) in Swift https://twitter.com/inamiy/status/1368468757702569986
struct State1 {
var foo1: Int
}
struct State2 {
var bar2: Bool
}
// Q. How to make a "flattened" `struct State1_2 { var foo1: Int, var bar2: Bool }`
// from `State1` and `State2`? (a.k.a. extensible record / intersection type)?
// A. Make a polymorphic `StateN` wrapper with adding `other` as a member variable.
@dynamicMemberLookup
struct PolymorphicState1<Other> {
var state1: State1
var other: Other
subscript<T>(dynamicMember keyPath: WritableKeyPath<State1, T>) -> T {
get { state1[keyPath: keyPath] }
set { state1[keyPath: keyPath] = newValue }
}
subscript<T>(dynamicMember keyPath: WritableKeyPath<Other, T>) -> T {
get { other[keyPath: keyPath] }
set { other[keyPath: keyPath] = newValue }
}
}
@dynamicMemberLookup
struct PolymorphicState2<Other> {
var state2: State2
var other: Other
subscript<T>(dynamicMember keyPath: WritableKeyPath<State2, T>) -> T {
get { state2[keyPath: keyPath] }
set { state2[keyPath: keyPath] = newValue }
}
subscript<T>(dynamicMember keyPath: WritableKeyPath<Other, T>) -> T {
get { other[keyPath: keyPath] }
set { other[keyPath: keyPath] = newValue }
}
}
// Now we are ready to define `State1_2`!
// Intersection of State1 & State2 (a.k.a. extensible records / intersection type)
typealias State1_2 = PolymorphicState1<PolymorphicState2<Void>>
let state1_2: State1_2 = PolymorphicState1(
state1: State1(foo1: 1),
other: PolymorphicState2(state2: State2(bar2: true), other: ())
)
state1_2.foo1
state1_2.bar2
// NOTE: `state1_2` looks like it has both `foo1` and `bar2` as member variables!
//----------------------------------------
// Now, the next question arises:
// Q. Can we do the same thing to make a "flattened enum"?
// A. Future Swift could maybe introduce new `@dynamicCaseLookup` and `CasePath` to fulfill this :)
enum Action1 {
case foo1(Int)
}
enum Action2 {
case bar2(Bool)
}
// NOTE: Not `@dynamicMemberLookup`
@dynamicCaseLookup
enum PolymorphicAction1<Other> {
case action1(Action1)
case other(Other)
subscript<T>(dynamicCase casePath: CasePath<Action1, T>) -> T? {
// Functional `Prism` handling comes here.
}
subscript<T>(dynamicCase casePath: CasePath<Other, T>) -> T? {
// Functional `Prism` handling comes here.
}
}
@dynamicCaseLookup
enum PolymorphicAction2<Other> {
case action2(Action2)
case other(Other)
subscript<T>(dynamicCase casePath: CasePath<Action2, T>) -> T? {
// Functional `Prism` handling comes here.
}
subscript<T>(dynamicCase casePath: CasePath<Other, T>) -> T? {
// Functional `Prism` handling comes here.
}
}
// Now we are ready to define `Action1_2`.
typealias Action1_2 = PolymorphicAction1<PolymorphicAction2<Never>>
// Union of Action1 & Action2 (a.k.a. extensible variants / union type)
let action1_2: Action1_2 = PolymorphicAction1.other(PolymorphicAction2.bar2(true))
// NOTE: Ideally, we can do exhaustive pattern matching here!
switch action1_2 {
case let .foo1(int): ...
case let .bar2(bool): ...
}
// Note that this technique is called "Row polymorphism" (popular in PureScript),
// which works very similar to how "intersection / union types" work in TypeScript.
@inamiy
Copy link
Author

inamiy commented Mar 7, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment