Sent from my iPad
> On Mar 17, 2017, at 5:17 PM, Michael LeHew <[email protected]> wrote: > > >>> On Mar 17, 2017, at 3:08 PM, Matthew Johnson <[email protected]> wrote: >>> >>> >>> On Mar 17, 2017, at 12:04 PM, Michael LeHew via swift-evolution >>> <[email protected]> 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. > > We had discussed that a future application where KeyPath's would make a lot > of sense is with the Mirror API. Of course in the interest of the finiteness > of time, we aren't pursuing that right now. > > One thing that gets interesting with the scope-restricted visibility of > KeyPaths, is what happens if an fileprivate KeyPath gets leaked out of the > file? That's a scary/maybe useful thing? But a complication that emerges > pretty quick and thus another reason not to pursue that just now. Yep, totally understand. :) The interaction with access control will definitely have some subtleties to consider. Is the plan to allow a type to manually vend a KeyPath wider than the visibility of properties in the path? It looks that way and that's probably the right call. > >> >>> >>> Many thanks, >>> -Michael >>> >>> Smart KeyPaths: Better Key-Value Coding for Swift >>> Proposal: SE-NNNN >>> Authors: David Smith, Michael LeHew, Joe Groff >>> Review Manager: TBD >>> Status: Awaiting Review >>> Associated PRs: >>> #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 >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
