> On Mar 17, 2017, at 3:08 PM, Matthew Johnson via swift-evolution > <swift-evolution@swift.org> wrote: > >> >> On Mar 17, 2017, at 12:04 PM, Michael LeHew via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >> >> Hi friendly swift-evolution folks, >> >> The Foundation and Swift team would like for you to consider the following >> proposal: > > This proposal is really incredible! It is an invaluable addition to the > language - far better than simple first-class properties. I really can’t > wait to see it implemented! The design looks very solid. I’m especially > happy to see that a path to eventually get away from using classes has > already been identified and planned for. > > Thank you so much for bringing this forward in Swift 4. It is a wonderful > (and rather unexpected) surprise! > > Seeing this makes me *really* wish we had a way to get at a collection of > `PartialKeyPath<Self>` for all the (visible) properties of a type. I guess > the visible part of that makes it tricky. We can always work around it in > the meantime.
Joe mentioned some ideas along these lines in the "Swift's reflection" thread today. Definitely seems like a solid direction to investigate. David > >> >> Many thanks, >> -Michael >> >> Smart KeyPaths: Better Key-Value Coding for Swift >> Proposal: SE-NNNN >> Authors: David Smith <https://github.com/Catfish-Man>, Michael LeHew >> <https://github.com/mlehew>, Joe Groff <https://github.com/jckarter> >> Review Manager: TBD >> Status: Awaiting Review >> Associated PRs: >> #644 <https://github.com/apple/swift-evolution/pull/644> >> Introduction >> We propose a family of concrete Key Path types that represent uninvoked >> references to properties that can be composed to form paths through many >> values and directly get/set their underlying values. >> >> Motivation >> We Can Do Better than String >> >> On Darwin platforms Swift's existing #keyPath() syntax provides a convenient >> way to safely refer to properties. Unfortunately, once validated, the >> expression becomes a String which has a number of important limitations: >> >> Loss of type information (requiring awkward Any APIs) >> Unnecessarily slow to parse >> Only applicable to NSObjects >> Limited to Darwin platforms >> Use/Mention Distinctions >> >> While methods can be referred to without invoking them (let x = foo.bar >> instead of let x = foo.bar()), this is not currently possible for >> properties and subscripts. >> >> Making indirect references to a properties' concrete types also lets us >> expose metadata about the property, and in the future additional behaviors. >> >> More Expressive KeyPaths >> >> We would also like to support being able to use Key Paths to access into >> collections, which is not currently possible. >> >> Proposed solution >> We propose introducing a new expression akin to Type.method, but for >> properties and subscripts. These property reference expressions produce >> KeyPath objects, rather than Strings. KeyPaths are a family of generic >> classes (structs and protocols here would be ideal, but requires generalized >> existentials) which encapsulate a property reference or chain of property >> references, including the type, mutability, property name(s), and ability to >> set/get values. >> >> Here's a sample of it in use: >> >> Swift >> struct Person { >> var name: String >> var friends: [Person] >> var bestFriend: Person? >> } >> >> var han = Person(name: "Han Solo", friends: []) >> var luke = Person(name: "Luke Skywalker", friends: [han]) >> >> let firstFriendsNameKeyPath = Person.friends[0].name >> >> let firstFriend = luke[path] // han >> >> // or equivalently, with type inferred from context >> let firstFriendName = luke[.friends[0].name] >> >> // rename Luke's first friend >> luke[firstFriendsNameKeyPath] = "A Disreputable Smuggler" >> >> let bestFriendsName = luke[.bestFriend]?.name // nil, if he is the last jedi >> Detailed design >> Core KeyPath Types >> >> KeyPaths are a hierarchy of progressively more specific classes, based on >> whether we have prior knowledge of the path through the object graph we wish >> to traverse. >> >> Unknown Path / Unknown Root Type >> >> AnyKeyPath is fully type-erased, referring to 'any route' through an >> object/value graph for 'any root'. Because of type-erasure many operations >> can fail at runtime and are thus nillable. >> >> Swift >> class AnyKeyPath: CustomDebugStringConvertible, Hashable { >> // MARK - Composition >> // Returns nil if path.rootType != self.valueType >> func appending(path: AnyKeyPath) -> AnyKeyPath? >> >> // MARK - Runtime Information >> class var rootType: Any.Type >> class var valueType: Any.Type >> >> static func == (lhs: AnyKeyPath, rhs: AnyKeyPath) -> Bool >> var hashValue: Int >> } >> Unknown Path / Known Root Type >> >> If we know a little more type information (what kind of thing the key path >> is relative to), then we can use PartialKeyPath <Root>, which refers to an >> 'any route' from a known root: >> >> Swift >> class PartialKeyPath<Root>: AnyKeyPath { >> // MARK - Composition >> // Returns nil if Value != self.valueType >> func appending(path: AnyKeyPath) -> PartialKeyPath<Root>? >> func appending<Value, AppendedValue>(path: KeyPath<Value, >> AppendedValue>) -> KeyPath<Root, AppendedValue>? >> func appending<Value, AppendedValue>(path: ReferenceKeyPath<Value, >> AppendedValue>) -> ReferenceKeyPath<Root, AppendedValue>? >> } >> Known Path / Known Root Type >> >> When we know both what the path is relative to and what it refers to, we can >> use KeyPath. Thanks to the knowledge of the Root and Value types, all of the >> failable operations lose their Optional. >> >> Swift >> public class KeyPath<Root, Value>: PartialKeyPath<Root> { >> // MARK - Composition >> func appending<AppendedValue>(path: KeyPath<Value, AppendedValue>) -> >> KeyPath<Root, AppendedValue> >> func appending<AppendedValue>(path: WritableKeyPath<Value, >> AppendedValue>) -> Self >> func appending<AppendedValue>(path: ReferenceWritableKeyPath<Value, >> AppendedValue>) -> ReferenceWritableKeyPath<Root, AppendedValue> >> } >> Value/Reference Mutation Semantics Mutation >> >> Finally, we have a pair of subclasses encapsulating value/reference mutation >> semantics. These have to be distinct because mutating a copy of a value is >> not very useful, so we need to mutate an inout value. >> >> Swift >> class WritableKeyPath<Root, Value>: KeyPath<Root, Value> { >> // MARK - Composition >> func appending<AppendedPathValue>(path: WritableKeyPath<Value, >> AppendedPathValue>) -> WritableKeyPath<Root, AppendedPathValue> >> } >> >> class ReferenceWritableKeyPath<Root, Value>: WritableKeyPath<Root, Value> { >> override func appending<AppendedPathValue>(path: WritableKeyPath<Value, >> AppendedPathValue>) -> ReferenceWritableKeyPath<Root, AppendedPathValue> >> } >> Access and Mutation Through KeyPaths >> >> To get or set values for a given root and key path we effectively add the >> following subscripts to all Swift types. >> >> Swift >> extension Any { >> subscript(path: AnyKeyPath) -> Any? { get } >> subscript<Root: Self>(path: PartialKeyPath<Root>) -> Any { get } >> subscript<Root: Self, Value>(path: KeyPath<Root, Value>) -> Value { get } >> subscript<Root: Self, Value>(path: WritableKeyPath<Root, Value>) -> >> Value { set, get } >> } >> This allows for code like >> >> Swift >> person[.name] // Self.type is inferred >> which is both appealingly readable, and doesn't require read-modify-write >> copies (subscripts access self inout). Conflicts with existing subscripts >> are avoided by using generic subscripts to specifically only accept key >> paths with a Root of the type in question. >> >> Referencing Key Paths >> >> Forming a KeyPath borrows from the same syntax used to reference methods and >> initializers,Type.instanceMethod only now working for properties and >> collections. Optionals are handled via optional-chaining. Multiply dotted >> expressions are allowed as well, and work just as if they were composed via >> the appending methods on KeyPath. >> >> There is no change or interaction with the #keyPath() syntax introduced in >> Swift 3. >> >> Performance >> >> The performance of interacting with a property via KeyPaths should be close >> to the cost of calling the property directly. >> >> Source compatibility >> This change is additive and there should no affect on existing source. >> >> Effect on ABI stability >> This feature adds the following requirements to ABI stability: >> >> mechanism to access key paths of public properties >> We think a protocol-based design would be preferable once the language has >> sufficient support for generalized existentials to make that ergonomic. By >> keeping the class hierarchy closed and the concrete implementations private >> to the implementation it should be tractable to provide compatibility with >> an open protocol-based design in the future. >> >> Effect on API resilience >> This should not significantly impact API resilience, as it merely provides a >> new mechanism for operating on existing APIs. >> >> Alternatives considered >> More Features >> >> Various drafts of this proposal have included additional features >> (decomposable key paths, prefix comparisons, support for custom KeyPath >> subclasses, creating a KeyPath from a String at runtime, KeyPaths conforming >> to Codable, bound key paths as a concrete type, etc.). We anticipate >> approaching these enhancements additively once the core KeyPath >> functionality is in place. >> >> Spelling >> >> We also explored many different spellings, each with different strengths. We >> have chosen the current syntax due to the balance with existing function >> type references. >> >> Current #keyPath Lisp-style >> Person.friends[0].name #keyPath(Person, .friends[0].name) >> `Person.friend.name >> luke[.friends[0].name] #keyPath(luke, .friends[0].name) >> luke`.friends[0].name >> luke.friends[0][.name] #keyPath(luke.friends[0], .name) >> luke.friends[0]`.name >> While the crispness is very appealing, the spelling of the 'escape' >> character was hard to agree upon (along with the fact that it requires >> parentheses to reduce ambiguity). #keyPath was very specific, but verbose >> especially when composing multiple key paths together. >> >> _______________________________________________ >> swift-evolution mailing list >> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >> https://lists.swift.org/mailman/listinfo/swift-evolution > > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org <mailto:swift-evolution@swift.org> > https://lists.swift.org/mailman/listinfo/swift-evolution > <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution