Closed Ryu0118 closed 1 year ago
Hey @Ryu0118! Thanks for taking the time to PR. This is actually a feature that folks request and try to implement regularly, most recently here: https://github.com/pointfreeco/swift-case-paths/pull/62
The main issue is that Swift doesn't seem to make it possible to implement a property with an optional getter and a non-optional setter, and if we use optionality everywhere, we lose some precision and introduce some nonsensical states:
result[/Result.success] = nil // What does this mean?
@iampatbrown got pretty close to getting things working the way we would want, but it still had a few issues, unfortunately.
@Ryu0118 The most recent experiment can be found here https://github.com/pointfreeco/swift-case-paths/compare/main...iampatbrown:swift-case-paths:case-accessible
It hasn't been thoroughly tested and it's not something I use, it's just an attempt to work around the limitations @stephencelis mentioned.
The following scenarios seemed to be working:
struct Baz: Equatable { var array: [Int] = [1, 2, 3], string: String = "Blob" }
indirect enum Foo: Equatable, CaseAccessible {
case foo(Foo)
case bar(Int)
case baz(Baz)
}
var foo = Foo.bar(42)
foo[/Foo.foo] = .baz(Baz()) // foo == .foo(.baz(Baz())
foo[/Foo.baz]?.string = "Blobby" // no-op
foo[/Foo.foo .. /Foo.bar] = 42 // foo == .foo(.bar(42))
foo[/Foo.baz] = Baz() // foo == .baz(Baz())
foo[/Foo.foo]?[/Foo.baz] = Baz() // no-op
foo[/Foo.foo .. /Foo.baz] = Baz() // foo == .foo(.baz(Baz())
foo[/Foo.foo]?[/Foo.baz]?.array = [3, 2, 1] // foo == .foo(.baz(Baz(array: [3, 2, 1]))
foo[/Foo.foo] // .baz(Baz(array: [3, 2, 1]))
foo[/Foo.foo]?[/Foo.baz] // Baz(array: [3, 2, 1])
foo[/Foo.foo]?[/Foo.baz]?.array // [3, 2, 1]
foo[/Foo.bar] // nil
foo[/Foo.baz] // nil
foo[/Foo.foo] = nil // 🛑 forbidden by the compiler
foo[/Foo.foo]?[/Foo.baz] = nil // 🛑 forbidden by the compiler
foo[/Foo.foo .. /Foo.baz] = nil // 🛑 forbidden by the compiler
foo[/Foo.foo] = .none // 🛑 forbidden by the compiler
foo[/Foo.foo] = nil as Foo? // 🛑 forbidden by the compiler
foo[/Foo.foo] = nil as _OptionallyChained<Foo>? // 🙃 no-op
foo[/Foo.bar] = nil as _OptionallyChained<Int>? // 🙃 no-op
By doing this I was able to solve the Optional problem, but could not solve the other problems :'(
public protocol Embeddable: Extractable {}
public extension Extractable {
subscript<Value>(casePath: CasePath<Self, Value>) -> Value? {
casePath.extract(from: self)
}
}
public extension Embeddable {
@_disfavoredOverload
subscript<Value>(casePath: CasePath<Self, Value>) -> Value {
@available(*, unavailable, message: "only available as optional getter")
get { fatalError() }
set { self = casePath.embed(newValue) }
}
subscript<Value: _OptionalProtocol>(casePath: CasePath<Self, Value>) -> Value {
get { casePath.extract(from: self)! }
set { self = casePath.embed(newValue) }
}
}
enum Foo: Embeddable {
case c1(Int?)
case c2(Int)
}
var e1 = Foo.c1(1)
var e2 = Foo.c2(2)
e1[/Foo.c1] = nil // e1 == .c1(nil)
e1[/Foo.c1] = 3 // e1 == .c1(3)
e2[/Foo.c2] = nil // forbidden by the compiler
e2[/Foo.c2] = 3 // e2 == .c2(3)
@Ryu0118 Thanks again for opening this and continuing the conversation! We plan on supporting functionality like this soon, but in a very different capacity (and are actively working on the case-key-paths
branch, if you are curious). As such, I'm going to close this for now, but thanks again for your original work on it!
Hi, I added Extractable and Embeddable protocols, which provide an interface similar to KeyPath.