Smart KeyPaths

I think that, as long as code within the `fileprivate` scope is responsible for passing the KeyPath out, that's an *extremely* useful thing. It essentially lets a privileged scope delegate its access to a less-privileged scope so it can do complex, potentially read-write work on its behalf. Imagine keeping an instance variable private, but allowing a serialization framework to access it through a key path; that'd be pretty handy.

···

On Mar 17, 2017, at 3:17 PM, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

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?

--
Brent Royal-Gordon
Architechies

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?

I think that, as long as code within the `fileprivate` scope is responsible for passing the KeyPath out, that's an *extremely* useful thing. It essentially lets a privileged scope delegate its access to a less-privileged scope so it can do complex, potentially read-write work on its behalf. Imagine keeping an instance variable private, but allowing a serialization framework to access it through a key path; that'd be pretty handy.

Would such KeyPaths be allowed to cross ABI boundaries? If so, then there may be both library evolution and ABI stability impact.

···

On Mar 17, 2017, at 3:24 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 3:17 PM, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

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)

How different would the design be with generalized existentials? Are they plans to migrate to that design once we do get 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

Perhaps I'm missing something, but what does that syntax bring compared to person.name? I see quite a few examples of the key paths being used literally in the subscript syntax but fail to see the usefulness of doing that. Can you give use cases?

The value comes from `.name` being a separate value from `person`. In the same way that closures let you abstract over functions and methods as plain old values independent of their original context, keypaths should let you do the same with a property.

-Joe

···

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think that's fine. It's no different from passing out a private function as a closure value, or passing out a value of a private type as a protocol existential or public base class instance.

-Joe

···

On Mar 17, 2017, at 3:17 PM, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 3:08 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> 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.

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.

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.

Yeah, that's a natural future extension. We're intentionally trying to keep the initial core functionality small to keep discussion focused (and also make good use of limited design and implementation time).

-Joe

···

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:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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

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.

I don't know how to express the level of "Yes" I feel in response to this proposal without using language that's inappropriate on a public mailing list.

A few questions:

How do key paths interact with Optionals? Can you form a key path to `Person.bestFriend.name`, and is that the syntax, or is it `Person.bestFriend?.name`?

Foundation key paths have a sometimes-useful property where, if they traverse a collection, the result becomes a collection of the ending type. Is a similar feature planned for smart key paths—perhaps something like `Person.friends.name`?

In many cases, you could add a property or subscript operation that has the same effect, e.g.:

extension Array {
  subscript<NewElement>(mapping key: KeyPath<Element, NewElement>) -> Array<NewElement> {
    return map { $0[key] }
  }
}

would let you write `Person.friends[mapping: .name]`. Before publishing the proposal, we had discussed having closure-based key path components. However, as Michael noted, an important capability of key paths in ObjC is that, while abstractly they're "just functions" in a sense, they're also introspectable and serializable values. Closures in Swift are by design opaque, so can't be equated, hashed, traversed by component, or serialized. Keeping key paths attached to declarations lets them preserve that structure, enabling their use for more interesting things like KVO in the future.

-Joe

···

On Mar 17, 2017, at 1:58 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 10:04 AM, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Given that Swift has a syntax called keypath which is unrelated to these "key paths", have you considered using a different name to avoid confusion? Maybe "property paths" or "accessor paths"?

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

So then this would be disambiguated like this?

let staticValue = Foo.bar // Defaults to accessing the static value, when there is ambiguity

let value: Bar = Foo.bar
let keyPath: WritableKeyPath<Foo, Bar> = Foo.bar

It’s a little unfortunately to have to spell out WritableKeyPath<Foo, Bar> there, but as long as there’s some way to do it, I don’t think it’s a problem. This is likely a rare edge case.

-BJ Homer

···

On Mar 17, 2017, at 3:56 PM, Joe Groff <jgroff@apple.com> wrote:

On Mar 17, 2017, at 2:53 PM, Michael LeHew <lehewjr@apple.com <mailto:lehewjr@apple.com>> wrote:

On Mar 17, 2017, at 2:21 PM, BJ Homer <bjhomer@gmail.com <mailto:bjhomer@gmail.com>> wrote:

This looks great!

What happens in the case when there is a static property by the same name as an instance property? e.g.

struct Foo {
    static var bar: Bar
    var bar: Bar
}

Foo.bar // What is this?

Is it still possible to reference both the static property and a KeyPath to the instance method?

This is essentially the same question that I arrived at in my reply to Vladimir. I think Joe might be best able to answer here.

We already encounter this situation with static vs instance methods, since `Foo.bar` can refer to either a static method `bar` or an unbound instance method `bar`. We use type context to disambiguate, favoring the static member if context doesn't help:

struct X {
  static func foo() {}
  func foo() {}
}

let foo1 = X.foo // Defaults to static member
let foo2: () -> () = X.foo // Picks static member by type context
let foo3: (X) -> () -> () = X.foo // Picks instance member by type context

-Joe

I think that kind of use case can be handled even by the previous property behaviors proposal, by having a behavior that presents a non-optional property that only allows initialization by being set once, and traps if gotten before being initialized.

-Joe

···

On Mar 17, 2017, at 4:05 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

On 17 Mar 2017, at 18:04, 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:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Very interesting! This sounds like it would enable a very nice design in a project I'm working on right now. I'm looking forward to seeing this take shape in the future.

It sounds like it wouldn't solve one use case I was hoping property behaviors might solve though. Sometimes we have a property that we would like to make a constant but cont because we can't set it until phase 2. I would really like to have some notion of a "phase 2 initialized constant" which would provide a much stronger guarantee than IUO does. There would be a very small window where a trap is possible between phase 1 and when it is set in phase 2. It would probably make more sense to actually think of this as phase 1.5 where all of these properties must be initialized to a non-nil value, but self can be used in the initializing expressions.

Yes, but perfectly closing those holes requires a much more complex type system than we have. Perfect is the enemy of good, and I think trapping on misuse gets you the lion's share of the benefit, and would still be a massive improvement over IUO.

-Joe

···

On Mar 17, 2017, at 4:25 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPhone

On Mar 17, 2017, at 6:09 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 17, 2017, at 4:05 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Sent from my iPad

On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

On 17 Mar 2017, at 18:04, 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:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Very interesting! This sounds like it would enable a very nice design in a project I'm working on right now. I'm looking forward to seeing this take shape in the future.

It sounds like it wouldn't solve one use case I was hoping property behaviors might solve though. Sometimes we have a property that we would like to make a constant but cont because we can't set it until phase 2. I would really like to have some notion of a "phase 2 initialized constant" which would provide a much stronger guarantee than IUO does. There would be a very small window where a trap is possible between phase 1 and when it is set in phase 2. It would probably make more sense to actually think of this as phase 1.5 where all of these properties must be initialized to a non-nil value, but self can be used in the initializing expressions.

I think that kind of use case can be handled even by the previous property behaviors proposal, by having a behavior that presents a non-optional property that only allows initialization by being set once, and traps if gotten before being initialized.

Didn't it require trapping if set more than once? And it didn't offer a way to require initialization during phase 2 did it?

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.

···

Sent from my iPad

On Mar 17, 2017, at 5:17 PM, Michael LeHew <lehewjr@apple.com> wrote:

On Mar 17, 2017, at 3:08 PM, Matthew Johnson <matthew@anandabits.com> wrote:
On Mar 17, 2017, at 12:04 PM, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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?

I think that, as long as code within the `fileprivate` scope is responsible for passing the KeyPath out, that's an *extremely* useful thing. It essentially lets a privileged scope delegate its access to a less-privileged scope so it can do complex, potentially read-write work on its behalf. Imagine keeping an instance variable private, but allowing a serialization framework to access it through a key path; that'd be pretty handy.

Agree. As long as it is manually vended it is semantically pretty similar to passing a closure that reads or writes. That's the way I would think about it.

···

Sent from my iPad

On Mar 17, 2017, at 5:24 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Mar 17, 2017, at 3:17 PM, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies

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.

Yeah, that's a natural future extension. We're intentionally trying to keep the initial core functionality small to keep discussion focused (and also make good use of limited design and implementation time).

That makes sense, especially considering the nuances around access control. I'm ecstatic to see even just the basic functionality making it into Swift 4!

···

Sent from my iPad

On Mar 17, 2017, at 5:45 PM, Joe Groff <jgroff@apple.com> wrote:

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> wrote:

-Joe

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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Very interesting! This sounds like it would enable a very nice design in a project I'm working on right now. I'm looking forward to seeing this take shape in the future.

It sounds like it wouldn't solve one use case I was hoping property behaviors might solve though. Sometimes we have a property that we would like to make a constant but cont because we can't set it until phase 2. I would really like to have some notion of a "phase 2 initialized constant" which would provide a much stronger guarantee than IUO does. There would be a very small window where a trap is possible between phase 1 and when it is set in phase 2. It would probably make more sense to actually think of this as phase 1.5 where all of these properties must be initialized to a non-nil value, but self can be used in the initializing expressions.

It sounds like property behaviors might not be the right way to solve that. If that is the case, is it possible a solution to this might be considered during Swift 4?

···

Sent from my iPad

On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

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)

How different would the design be with generalized existentials? Are they plans to migrate to that design once we do get 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

Perhaps I'm missing something, but what does that syntax bring compared to person.name? I see quite a few examples of the key paths being used literally in the subscript syntax but fail to see the usefulness of doing that. Can you give use cases?

The value comes from `.name` being a separate value from `person`. In the same way that closures let you abstract over functions and methods as plain old values independent of their original context, keypaths should let you do the same with a property.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent from my iPad

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Very interesting! This sounds like it would enable a very nice design in a project I'm working on right now. I'm looking forward to seeing this take shape in the future.

It sounds like it wouldn't solve one use case I was hoping property behaviors might solve though. Sometimes we have a property that we would like to make a constant but cont because we can't set it until phase 2. I would really like to have some notion of a "phase 2 initialized constant" which would provide a much stronger guarantee than IUO does. There would be a very small window where a trap is possible between phase 1 and when it is set in phase 2. It would probably make more sense to actually think of this as phase 1.5 where all of these properties must be initialized to a non-nil value, but self can be used in the initializing expressions.

I think that kind of use case can be handled even by the previous property behaviors proposal, by having a behavior that presents a non-optional property that only allows initialization by being set once, and traps if gotten before being initialized.

Didn't it require trapping if set more than once? And it didn't offer a way to require initialization during phase 2 did it?

···

Sent from my iPhone

On Mar 17, 2017, at 6:09 PM, Joe Groff <jgroff@apple.com> wrote:

On Mar 17, 2017, at 4:05 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:

-Joe

Sent from my iPhone

Sent from my iPad

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Very interesting! This sounds like it would enable a very nice design in a project I'm working on right now. I'm looking forward to seeing this take shape in the future.

It sounds like it wouldn't solve one use case I was hoping property behaviors might solve though. Sometimes we have a property that we would like to make a constant but cont because we can't set it until phase 2. I would really like to have some notion of a "phase 2 initialized constant" which would provide a much stronger guarantee than IUO does. There would be a very small window where a trap is possible between phase 1 and when it is set in phase 2. It would probably make more sense to actually think of this as phase 1.5 where all of these properties must be initialized to a non-nil value, but self can be used in the initializing expressions.

I think that kind of use case can be handled even by the previous property behaviors proposal, by having a behavior that presents a non-optional property that only allows initialization by being set once, and traps if gotten before being initialized.

Didn't it require trapping if set more than once? And it didn't offer a way to require initialization during phase 2 did it?

Yes, but perfectly closing those holes requires a much more complex type system than we have. Perfect is the enemy of good, and I think trapping on misuse gets you the lion's share of the benefit, and would still be a massive improvement over IUO.

Yes, I agree that it would be a massive improvement. I just wish there was a way to solve this that didn’t require fancy type system stuff. We already have relatively complex initialization rules that exist outside the type system that track initialization. It would be nice if we could solve this using a small enhancement to those rules. For example, we could have some kind of attribute or access control that is only available for IUO properties that restricts the setter to only be available in the initializer and guarantees that it is set to non-nil on exit of init. Maybe it’s too much of a special case to do this, but it would be really cool to have this hole plugged.

···

On Mar 17, 2017, at 6:29 PM, Joe Groff <jgroff@apple.com> wrote:

On Mar 17, 2017, at 4:25 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
On Mar 17, 2017, at 6:09 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Mar 17, 2017, at 4:05 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-Joe

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
Proposal: SE-NNNN
Authors: David Smith <https://github.com/Catfish-Man&gt;, Michael LeHew <https://github.com/mlehew&gt;, Joe Groff <https://github.com/jckarter&gt;
Review Manager: TBD
Status: Awaiting Review
Associated PRs:
#644 <https://github.com/apple/swift-evolution/pull/644&gt;
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

Would these user-defined key path types be unbound like the ones described in the current proposal? That would be really cool.

I am thinking through how this would impact the design of a library I’m working on. It is a use case where I would want a single unbound instance per property rather than an instance of the key path (and its storage) for each instance of the type. Storage of the property value itself would not be affected by the behavior.

I would use reflection to discover the key paths of a type that my library recognizes and use them to interact with the property. I would have the user initialize the custom key path for a property with a key path into a related type where both have the same value type. The library would use the key paths to synchronize data between the related instances. There is no need to change the per-instance storage or access (the library would not rely on intercepting specific accesses to the property to synchronize the data). It would only be used as a powerful tool for working with arbitrary types.

···

On Mar 17, 2017, at 5:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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)

How different would the design be with generalized existentials? Are they plans to migrate to that design once we do get 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

Perhaps I'm missing something, but what does that syntax bring compared to person.name? I see quite a few examples of the key paths being used literally in the subscript syntax but fail to see the usefulness of doing that. Can you give use cases?

The value comes from `.name` being a separate value from `person`. In the same way that closures let you abstract over functions and methods as plain old values independent of their original context, keypaths should let you do the same with a property.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following proposal:

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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I also see key path objects as a good way of eventually addressing some of the design problems we ran up against with property behaviors (https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md from last year), including the problem of what exactly a property behavior declaration *is* (a type? a protocol? a function-like thing? something completely new?), and the problem of handling "out-of-band" operations on a property beyond getting and setting, such as clearing a cached lazy value, registering for notifications on an observable property, and so on. I think it would be natural to express property behaviors as a user-defined key path type; the key path type can provide the get/set logic for the property as well as any other interesting operations the property supports. This answers the questions of both what behaviors look like (they're just types that conform to KeyPath) and how they extend properties with new actions (they're just methods of the key path value) fairly nicely.

That sounds very elegant!

···

On 17 Mar 2017, at 23:38, Joe Groff <jgroff@apple.com> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org> wrote:
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)

How different would the design be with generalized existentials? Are they plans to migrate to that design once we do get 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

Perhaps I'm missing something, but what does that syntax bring compared to person.name? I see quite a few examples of the key paths being used literally in the subscript syntax but fail to see the usefulness of doing that. Can you give use cases?

The value comes from `.name` being a separate value from `person`. In the same way that closures let you abstract over functions and methods as plain old values independent of their original context, keypaths should let you do the same with a property.

-Joe

This might be my new favorite Swift feature. <3 It seems obvious in retrospect, which is surely a sign of its genius.

I would LOVE to be able to get the individual components in a path. Even if it were just the names, that would let me do some things that I’ve been itching to do in Swift. If the types were exposed in a type-erased container, that would be even better.

Something like this would be plenty of information to accomplish my goals:

    class AnyKeyPath {
        …

        struct Key {
            let name: String
            let rootType: Any.Type
            let valueType: Any.Type
        }

        private(set) var keys: [Key]
    }

But even stringifying a key path would probably let me do most of what I wanted. I’d just need to (somewhat awkwardly) split on `.`s and handle subscripts. You mentioned that we’d have “limited” ability to stringify a key path. Can you elaborate on what that means or what the limitations would be?

Matt

···

On Mar 17, 2017, at 6:38 PM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 17, 2017, at 12:34 PM, David Hart via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sent off-list by mistake:

Nice proposal. I have a few comments inline:

On 17 Mar 2017, at 18:04, Michael LeHew via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
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.

What metadata is attached? How is it accessed? What future features are you thinking about?

To begin with, you'd have limited ability to stringify a key path. Eventually we'd like to support other reflectiony things, including:

- Asking for the primary key paths a type supports
- Asking for a key by name or index
- Breaking a key path down by components

I love the proposal and it is great to see this feature being considered. This provides a great foundation for future functionality. I too find the syntax confusing. The syntax is elegant and does not introduce any extra keywords, but accessing key paths is an advanced “meta” feature and does not have to be this straightforward. I would prefer less potential ambiguity at the expense of a little extra verbosity.

I suggest that the all keypaths be grouped under a `keypaths` pseudo-root path. This eliminates potential ambiguity with other language constructs and is easier for humans ( and as an added benefit, the compiler) to parse, reducing potential errors and clearer error diagnostics for name collisions and misspellings. Compiler reasoning and error messages are difficult enough deal with today. The use of a unique root for all keypaths will also greatly simplify searching for uses and code maintenance.

Many of the advanced language features that have been proposed for Swift add elegance and power to the language, but they have a cost in comprehensibility especially to beginners. The use of `keypaths` greatly improves the discoverability of this feature.

Another potential benefit of introducing a keyword-like construct now could simplifies the task of adding more metadata with other pseudo-root paths like `classKeypaths` and others as the language evolves.

Here is your example, rewritten with `keypaths` highlighted as a keyword. It does not add any additional nesting or operators, but clearly identifies the construct as something different and provides a simple term to search for in documentation.

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.keypaths.friends[0].name

let firstFriend = luke[keypaths.path] // han

// or equivalently, with type inferred from context
let firstFriendName = luke[keypaths.friends[0].name]

// rename Luke's first friend
luke[firstFriendsNameKeyPath] = "A Disreputable Smuggler"

let bestFriendsName = luke[keypaths.bestFriend]?.name // nil, if he is the last jedi

···

On Mar 17, 2017, at 12:27 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:

On 17.03.2017 20:04, Michael LeHew via swift-evolution wrote:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following
proposal:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
...

Just my 2 cents. FWIW, I feel like the proposed syntax is VERY confusing.

Person.friends[0].name - like access to static property. T.method can't have subscript for it and then some field. So, I believe such key paths deserve its own syntax.

luke[path] - very close to access the subscript of instance. In compare to T.method, we can call result of T.method(i.e. let m = T.method; m()), just like the T.method itself(foo.method()). But here we are calling some kind of subscript instead of access property by "path".

There is no some 'special' marker for me that saying "hey, there is references to properties used" from the first look.

Probably we can consider another separators, like

Person:friends[0].name
luke:path

Person->friends[0].name
luke->path

Person[friends[0].name] // single brackets for type
luke[[path]] // double brackets for instance

Person@friends[0].name
luke@path

or other with help of community.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I agree that they can get mixed up with static properties. However, I think I would not mind because it would only cause an ambiguity when having a static property with the same name as the property which would be an odd practice I think.

I was defining static properties with the same name as the property in order to smart key paths. For example:

Class Person {
     static firstName: KeyPath<String>(“firstName”)
     static lastName: KeyPath<String>(“lastName”)

     var firstName: String
     var lastName: String

     …
}

So that I could create qualifiers, ie. Person.firstName.like(“R*”), and sort orderings, Person.firstName.asc().

I think with these smart key paths the need for these static properties that mirror the property on the instance would not be required.

···

On Mar 17, 2017, at 2:27 PM, Vladimir.S via swift-evolution <swift-evolution@swift.org> wrote:

On 17.03.2017 20:04, Michael LeHew via swift-evolution wrote:

Hi friendly swift-evolution folks,

The Foundation and Swift team would like for you to consider the following
proposal:

Many thanks,
-Michael

Smart KeyPaths: Better Key-Value Coding for Swift
...

Just my 2 cents. FWIW, I feel like the proposed syntax is VERY confusing.

Person.friends[0].name - like access to static property. T.method can't have subscript for it and then some field. So, I believe such key paths deserve its own syntax.

luke[path] - very close to access the subscript of instance. In compare to T.method, we can call result of T.method(i.e. let m = T.method; m()), just like the T.method itself(foo.method()). But here we are calling some kind of subscript instead of access property by "path".

There is no some 'special' marker for me that saying "hey, there is references to properties used" from the first look.

Probably we can consider another separators, like

Person:friends[0].name
luke:path

Person->friends[0].name
luke->path

Person[friends[0].name] // single brackets for type
luke[[path]] // double brackets for instance

Person@friends[0].name
luke@path

or other with help of community.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution