Created
March 7, 2021 08:00
-
-
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
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
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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See also: https://github.com/inamiy/ExtensibleStruct