[Draft] Obejctive-C Keypaths

Hello Swift-friends,

After more thinking, here is a new proposal, based on my original proposal for #selector for properties but better targeted at the intended use: referencing key-paths. Please let me know what you think:

Referencing Objective-C key-paths

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-objc-keypaths.md&gt;
Author(s): David Hart <https://github.com/hartbit&gt;
Status: TBD
Review manager: TBD
<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#introduction&gt;Introduction

In Objective-C and Swift, key-paths used by KVC and KVO are represented as string literals (e.g., "friend.address.streetName"). This proposal seeks to improve the safety and resilience to modification of code using key-paths by introducing a compiler-checked expression.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#motivation&gt;Motivation

The use of string literals for key paths is extremely error-prone: there is no checking that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression SE-0022 <https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md&gt;, this proposal introduces a syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#proposed-solution&gt;Proposed solution

Introduce a new expression #keypath that allows one to build a String from a key-path:

class Person: NSObject {
    dynamic var firstName: String = ""
    dynamic var lastName: String = ""
    dynamic var friends: [Person] =
    dynamic var bestFriend: Person?

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

let chris = Person(firstName: "Chris", lastName: "Lattner")
let joe = Person(firstName: "Joe", lastName: "Groff")
let douglas = Person(firstName: "Douglas", lastName: "Gregor")
chris.friends = [joe, douglas]
chris.bestFriend = joe

#keypath(Person.firstName) // => "firstName"
chris.valueForKey(#keypath(Person.firstName)) // => Chris
#keypath(Person.bestFriend.lastName) // => "bestFriend.lastName"
chris.valueForKeyPath(#keypath(Person.bestFriend.lastName)) // => Groff
#keypath(Person.friends.firstName) // => "friends.firstName"
chris.valueForKeyPath(#keypath(Person.friends.firstName)) // => ["Joe", "Douglas"]
By having the #keypath expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#impact-on-existing-code&gt;Impact on existing code

The introduction of the #keypath expression has no impact on existing code as it returns a String. It is simply a modification-safe alternative to using literal strings for referencing key-paths.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#alternatives-considered&gt;Alternatives considered

One aspect of the design which seems potentially complicated is the reference to key-paths which include an collection in the middle of the path.

chris.valueForKeyPath(#keypath(Person.friends.firstName))
The above example is potentially harder to implement because the argument of #keypath is not a valid Swift expression, compared to the other two examples. An alternative would be to remove the ability to reference those key-paths, making the proposal less useful, but easier to implement.

I think that using something like keypath would be safer and more refactor-friendly than bare strings, but is KVC going to be an important part of Swift's future?

Félix

···

Le 4 mars 2016 à 12:05:05, David Hart via swift-evolution <swift-evolution@swift.org> a écrit :

Hello Swift-friends,

After more thinking, here is a new proposal, based on my original proposal for selector for properties but better targeted at the intended use: referencing key-paths. Please let me know what you think:

Referencing Objective-C key-paths

Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/XXXX-objc-keypaths.md&gt;
Author(s): David Hart <https://github.com/hartbit&gt;
Status: TBD
Review manager: TBD
<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#introduction&gt;Introduction

In Objective-C and Swift, key-paths used by KVC and KVO are represented as string literals (e.g., "friend.address.streetName"). This proposal seeks to improve the safety and resilience to modification of code using key-paths by introducing a compiler-checked expression.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#motivation&gt;Motivation

The use of string literals for key paths is extremely error-prone: there is no checking that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression SE-0022 <https://github.com/apple/swift-evolution/blob/master/proposals/0022-objc-selectors.md&gt;, this proposal introduces a syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#proposed-solution&gt;Proposed solution

Introduce a new expression keypath that allows one to build a String from a key-path:

class Person: NSObject {
    dynamic var firstName: String = ""
    dynamic var lastName: String = ""
    dynamic var friends: [Person] =
    dynamic var bestFriend: Person?

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

let chris = Person(firstName: "Chris", lastName: "Lattner")
let joe = Person(firstName: "Joe", lastName: "Groff")
let douglas = Person(firstName: "Douglas", lastName: "Gregor")
chris.friends = [joe, douglas]
chris.bestFriend = joe

keypath(Person.firstName) // => "firstName"
chris.valueForKey(keypath(Person.firstName)) // => Chris
keypath(Person.bestFriend.lastName) // => "bestFriend.lastName"
chris.valueForKeyPath(keypath(Person.bestFriend.lastName)) // => Groff
keypath(Person.friends.firstName) // => "friends.firstName"
chris.valueForKeyPath(keypath(Person.friends.firstName)) // => ["Joe", "Douglas"]
By having the keypath expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#impact-on-existing-code&gt;Impact on existing code

The introduction of the keypath expression has no impact on existing code as it returns a String. It is simply a modification-safe alternative to using literal strings for referencing key-paths.

<https://github.com/hartbit/swift-evolution/tree/objc-keypaths#alternatives-considered&gt;Alternatives considered

One aspect of the design which seems potentially complicated is the reference to key-paths which include an collection in the middle of the path.

chris.valueForKeyPath(keypath(Person.friends.firstName))
The above example is potentially harder to implement because the argument of keypath is not a valid Swift expression, compared to the other two examples. An alternative would be to remove the ability to reference those key-paths, making the proposal less useful, but easier to implement.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I think KVC is going to continue being useful:
* Foundation is becoming part of the Swift portability story
* We are still at least a year and a half (Swift 4) from the kind of features which will reduce the usefulness of KVO and KVC.

It is also evident that smoothing out working with Objective-C is still greatly sought out as the selector proposal shows.

···

On 04 Mar 2016, at 18:55, Félix Cloutier <felixcca@yahoo.ca> wrote:

I think that using something like keypath would be safer and more refactor-friendly than bare strings, but is KVC going to be an important part of Swift's future?

Félix

Le 4 mars 2016 à 12:05:05, David Hart via swift-evolution <swift-evolution@swift.org> a écrit :

Hello Swift-friends,

After more thinking, here is a new proposal, based on my original proposal for selector for properties but better targeted at the intended use: referencing key-paths. Please let me know what you think:

Referencing Objective-C key-paths
Proposal: SE-XXXX
Author(s): David Hart
Status: TBD
Review manager: TBD
Introduction

In Objective-C and Swift, key-paths used by KVC and KVO are represented as string literals (e.g., "friend.address.streetName"). This proposal seeks to improve the safety and resilience to modification of code using key-paths by introducing a compiler-checked expression.

Motivation

The use of string literals for key paths is extremely error-prone: there is no checking that the string corresponds to a valid key-path. In a similar manner to the proposal for the Objective-C selector expression SE-0022, this proposal introduces a syntax for referencing compiler-checked key-paths. When the referenced properties and methods are renamed or deleted, the programmer will be notified by a compiler error.

Proposed solution

Introduce a new expression keypath that allows one to build a String from a key-path:

class Person: NSObject {
    dynamic var firstName: String = ""
    dynamic var lastName: String = ""
    dynamic var friends: [Person] =
    dynamic var bestFriend: Person?

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

let chris = Person(firstName: "Chris", lastName: "Lattner")
let joe = Person(firstName: "Joe", lastName: "Groff")
let douglas = Person(firstName: "Douglas", lastName: "Gregor")
chris.friends = [joe, douglas]
chris.bestFriend = joe

keypath(Person.firstName) // => "firstName"
chris.valueForKey(keypath(Person.firstName)) // => Chris
keypath(Person.bestFriend.lastName) // => "bestFriend.lastName"
chris.valueForKeyPath(keypath(Person.bestFriend.lastName)) // => Groff
keypath(Person.friends.firstName) // => "friends.firstName"
chris.valueForKeyPath(keypath(Person.friends.firstName)) // => ["Joe", "Douglas"]
By having the keypath expression do the work to form the Objective-C key-path string, we free the developer from having to do the manual typing and get static checking that the key-path exists and is exposed to Objective-C.

Impact on existing code

The introduction of the keypath expression has no impact on existing code as it returns a String. It is simply a modification-safe alternative to using literal strings for referencing key-paths.

Alternatives considered

One aspect of the design which seems potentially complicated is the reference to key-paths which include an collection in the middle of the path.

chris.valueForKeyPath(keypath(Person.friends.firstName))
The above example is potentially harder to implement because the argument of keypath is not a valid Swift expression, compared to the other two examples. An alternative would be to remove the ability to reference those key-paths, making the proposal less useful, but easier to implement.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

* Foundation is becoming part of the Swift portability story

Foundation is, but KVC is not—Corelibs Foundation does not appear to support it (or even have the stubs for it).

Nevertheless, I do actually think this is a useful feature even without KVC. It is often the case that, in serializing an instance, you need to create fields with names matching the instance's properties. This is true whether you're using NSCoding, RawRepresentable with a dictionary raw value, or something completely custom.

The simplest way to do this is to type a string literal in each place. But that means each name is now duplicated in three places: the property declaration, the writing code, and the reading code. You can reduce that to two by creating constants for the property names, but that has a few issues:

1. There are still two sources of truth (the constants and the properties).
2. It requires a fair bit of discipline and foresight; it's something you have to learn is a good idea, usually through experience.
3. It's kind of a pain.

Supporting something like keypath() even in environments without Objective-C would allow you to write this kind of code using one source of truth: the property declarations.

(If we did this, the use of `.` for multi-component key paths would be an arbitrary choice on non-Objective-C platforms. But it's as good as any choice. Certain types, like Optional and Array, would have to be marked as "transparent" to the feature; they would be considered equivalent to their generic type.)

If we went this route, I would suggest changing a few things here.

1. keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

  print(#key(Person.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(chris.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

  print(Person.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(chris.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

For this receiver-based form, we might want to repurpose leading dot to refer to the type of the receiver of the method it's being passed to, if it is being passed to a method. In other words, you could write this:

  chris.valueForKeyPath(.#key(firstName))

And it would know to look in `Person` because `chris` is a `Person`. This doesn't really match the conventional meaning of leading dot, but I don't see a way to apply leading dot's usual meaning to this construct anyway.

···

--
Brent Royal-Gordon
Architechies

If we went this route, I would suggest changing a few things here.

1. keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

Sounds good to me!

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

I don’t think this is a good idea because:
- #name, property, #key all hide the ability to chain properties for a “key path”.
- keypath already has prior meaning to so many developers.

Even if keypath exists without the Objective-C runtime, I think that the name is strong enough meaning to keep it’s weight. What do you think?

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

  print(#key(Person.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(chris.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

  print(Person.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(chris.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

I understand the reasoning behind this modification, I would also like to be able to reference properties from variables (instead of by type), but I find both propositions confusing. I agree that with the first version, it is very tricky on first sight to understand what is being “keypathed”. But I find the second variation syntactically very surprising for Swift and I think many people in the mailing list would have problems with it.

Here’s a potential solution: force explicit self. inside keypath expressions:

print(keypath(Person.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(chris.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(self.bestFriend.firstName) // => bestFriend.firstName

Better?

David.

I updated my proposal to mention value-expressions, but I since saw that my self. proposition was a bit stupid.

https://github.com/hartbit/swift-evolution/blob/objc-keypaths/proposals/XXXX-objc-keypaths.md

···

On 08 Mar 2016, at 08:21, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

If we went this route, I would suggest changing a few things here.

1. keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

Sounds good to me!

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

I don’t think this is a good idea because:
- #name, property, #key all hide the ability to chain properties for a “key path”.
- keypath already has prior meaning to so many developers.

Even if keypath exists without the Objective-C runtime, I think that the name is strong enough meaning to keep it’s weight. What do you think?

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

  print(#key(Person.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(chris.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

  print(Person.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(chris.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

I understand the reasoning behind this modification, I would also like to be able to reference properties from variables (instead of by type), but I find both propositions confusing. I agree that with the first version, it is very tricky on first sight to understand what is being “keypathed”. But I find the second variation syntactically very surprising for Swift and I think many people in the mailing list would have problems with it.

Here’s a potential solution: force explicit self. inside keypath expressions:

print(keypath(Person.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(chris.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(self.bestFriend.firstName) // => bestFriend.firstName

Better?

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

You may also want to consider how this works with collection operators such as @sum and @unionOfObjects: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html

Jack

···

On Mar 9, 2016, at 11:52 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

I updated my proposal to mention value-expressions, but I since saw that my self. proposition was a bit stupid.

https://github.com/hartbit/swift-evolution/blob/objc-keypaths/proposals/XXXX-objc-keypaths.md

On 08 Mar 2016, at 08:21, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

If we went this route, I would suggest changing a few things here.

1. keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

Sounds good to me!

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

I don’t think this is a good idea because:
- #name, property, #key all hide the ability to chain properties for a “key path”.
- keypath already has prior meaning to so many developers.

Even if keypath exists without the Objective-C runtime, I think that the name is strong enough meaning to keep it’s weight. What do you think?

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

  print(#key(Person.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(chris.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

  print(Person.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(chris.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

I understand the reasoning behind this modification, I would also like to be able to reference properties from variables (instead of by type), but I find both propositions confusing. I agree that with the first version, it is very tricky on first sight to understand what is being “keypathed”. But I find the second variation syntactically very surprising for Swift and I think many people in the mailing list would have problems with it.

Here’s a potential solution: force explicit self. inside keypath expressions:

print(keypath(Person.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(chris.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(self.bestFriend.firstName) // => bestFriend.firstName

Better?

David.
_______________________________________________
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

I purposely left it out because it's either material for a future proposal, or not useful enough to implement in Swift.

···

Sent from my iPhone

On 10 Mar 2016, at 09:29, Jack Lawrence <jackl@apple.com> wrote:

You may also want to consider how this works with collection operators such as @sum and @unionOfObjects: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html

Jack

On Mar 9, 2016, at 11:52 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

I updated my proposal to mention value-expressions, but I since saw that my self. proposition was a bit stupid.

https://github.com/hartbit/swift-evolution/blob/objc-keypaths/proposals/XXXX-objc-keypaths.md

On 08 Mar 2016, at 08:21, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

If we went this route, I would suggest changing a few things here.

1. keypath should be equivalent to a string literal, not necessarily a `Swift.String`. That would allow you to store it into StaticString or other StringLiteralConvertible types. (If you had an enum corresponding to the available properties, you might even be able to conform it to StringLiteralConvertible and use this feature with it.)

Sounds good to me!

2. We *may* want to consider calling it something that's slightly less tied to Cocoa terminology, like #name or property. (#key might be a good compromise between the two.) I don't think this is *necessary*, but we might want to consider it.

I don’t think this is a good idea because:
- #name, property, #key all hide the ability to chain properties for a “key path”.
- keypath already has prior meaning to so many developers.

Even if keypath exists without the Objective-C runtime, I think that the name is strong enough meaning to keep it’s weight. What do you think?

3. It would be nice if you could easily get a name for a property on `self`, and it might even make sense to allow you to easily get a name for a property on any variable. Maybe the syntax (assuming that this code is inside a `Person`) would be more like:

  print(#key(Person.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(chris.bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

Note that that last form has a slightly tricky rule: because the only visible `bestFriend` is a property, `#name(bestFriend.firstName)` is actually `#name(self.bestFriend.firstName)`, so the `self` is what's stripped off. A small redesign would disambiguate:

  print(Person.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(chris.#key(bestFriend.firstName)) // => bestFriend.firstName
  print(#key(bestFriend.firstName) // => bestFriend.firstName

I understand the reasoning behind this modification, I would also like to be able to reference properties from variables (instead of by type), but I find both propositions confusing. I agree that with the first version, it is very tricky on first sight to understand what is being “keypathed”. But I find the second variation syntactically very surprising for Swift and I think many people in the mailing list would have problems with it.

Here’s a potential solution: force explicit self. inside keypath expressions:

print(keypath(Person.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(chris.bestFriend.firstName)) // => bestFriend.firstName
print(keypath(self.bestFriend.firstName) // => bestFriend.firstName

Better?

David.
_______________________________________________
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