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

Reply via email to