[swift-evolution] Smart KeyPaths

Zach Waldowski zach at waldowski.me
Sun Mar 19 13:04:06 CDT 2017

I might point out that this form of bikeshedding was already explicitly
called out in the text of the proposal. ("Alternatives Considered")
Your personal preferences as to what sigils are "lightweight" or "easy"
are not absolute ones without some form of evidence or objective
rationale provided.

Until that can be done, it would be more productive to discuss what we
can do to avoid ambiguity without magic sigils; or, in the face of Joe
Groff's post, why that ambiguity is a problem at all.


  Zachary Waldowski

  zach at waldowski.me

On Sun, Mar 19, 2017, at 01:00 AM, Muse M via swift-evolution wrote:

> I would suggest a keypath using ~ which is concise and clearer to
> identify.

> let myPath = Person~friends[0].name






> On Sunday, March 19, 2017, Jonathan Hull via swift-evolution <swift-
> evolution at swift.org> wrote:
>> This looks fantastic!


>> The one worry I would have, as others have brought up, is confusion
>> when there are static properties with the same name.  Maybe we could
>> have a special static property called ‘keyPath’ or ‘path’ that would
>> be reserved, and anything that followed would be a key path.

>> So instead of:


>> let myPath = Person.friends[0].name


>> you would have:


>> let myPath = Person.keyPath.friends[0].name



>> Or if we want to choose a sentinel, I would nominate ‘$’:


>> let myPath = $Person.friends[0].name


>> Thanks,

>> Jon



>>> On Mar 17, 2017, at 10:04 AM, Michael LeHew via swift-evolution <swift-
>>> evolution at 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[1], Michael LeHew[2], Joe Groff[3]
>>>  * Review Manager: TBD
>>>  * Status: *Awaiting Review*
>>>  * Associated PRs:
>>>    * #644[4]
>>> 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[].name

>>> let firstFriend = luke[path] // han

>>> // or equivalently, with type inferred from context let
>>> firstFriendName = luke[.friends[].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
> _________________________________________________

> swift-evolution mailing list

> swift-evolution at swift.org

> https://lists.swift.org/mailman/listinfo/swift-evolution


  1. https://github.com/Catfish-Man
  2. https://github.com/mlehew
  3. https://github.com/jckarter
  4. https://github.com/apple/swift-evolution/pull/644
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170319/f4c4389e/attachment.html>

More information about the swift-evolution mailing list