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?
-BJ
> On Mar 17, 2017, at 11:04 AM, Michael LeHew via swift-evolution
> <[email protected]> wrote:
>
> Hi friendly swift-evolution folks,
>
> The Foundation and Swift team would like for you to consider the following
> proposal:
>
> Many thanks,
> -Michael
>
> Smart KeyPaths: Better Key-Value Coding for Swift
> Proposal: SE-NNNN
> Authors: David Smith <https://github.com/Catfish-Man>, Michael LeHew
> <https://github.com/mlehew>, Joe Groff <https://github.com/jckarter>
> Review Manager: TBD
> Status: Awaiting Review
> Associated PRs:
> #644 <https://github.com/apple/swift-evolution/pull/644>
> Introduction
> We propose a family of concrete Key Path types that represent uninvoked
> references to properties that can be composed to form paths through many
> values and directly get/set their underlying values.
>
> Motivation
> We Can Do Better than String
>
> On Darwin platforms Swift's existing #keyPath() syntax provides a convenient
> way to safely refer to properties. Unfortunately, once validated, the
> expression becomes a String which has a number of important limitations:
>
> Loss of type information (requiring awkward Any APIs)
> Unnecessarily slow to parse
> Only applicable to NSObjects
> Limited to Darwin platforms
> Use/Mention Distinctions
>
> While methods can be referred to without invoking them (let x = foo.bar
> instead of let x = foo.bar()), this is not currently possible for properties
> and subscripts.
>
> Making indirect references to a properties' concrete types also lets us
> expose metadata about the property, and in the future additional behaviors.
>
> More Expressive KeyPaths
>
> We would also like to support being able to use Key Paths to access into
> collections, which is not currently possible.
>
> Proposed solution
> We propose introducing a new expression akin to Type.method, but for
> properties and subscripts. These property reference expressions produce
> KeyPath objects, rather than Strings. KeyPaths are a family of generic
> classes (structs and protocols here would be ideal, but requires generalized
> existentials) which encapsulate a property reference or chain of property
> references, including the type, mutability, property name(s), and ability to
> set/get values.
>
> Here's a sample of it in use:
>
> Swift
> struct Person {
> var name: String
> var friends: [Person]
> var bestFriend: Person?
> }
>
> var han = Person(name: "Han Solo", friends: [])
> var luke = Person(name: "Luke Skywalker", friends: [han])
>
> let firstFriendsNameKeyPath = Person.friends[0].name
>
> let firstFriend = luke[path] // han
>
> // or equivalently, with type inferred from context
> let firstFriendName = luke[.friends[0].name]
>
> // rename Luke's first friend
> luke[firstFriendsNameKeyPath] = "A Disreputable Smuggler"
>
> let bestFriendsName = luke[.bestFriend]?.name // nil, if he is the last jedi
> Detailed design
> Core KeyPath Types
>
> KeyPaths are a hierarchy of progressively more specific classes, based on
> whether we have prior knowledge of the path through the object graph we wish
> to traverse.
>
> Unknown Path / Unknown Root Type
>
> AnyKeyPath is fully type-erased, referring to 'any route' through an
> object/value graph for 'any root'. Because of type-erasure many operations
> can fail at runtime and are thus nillable.
>
> Swift
> class AnyKeyPath: CustomDebugStringConvertible, Hashable {
> // MARK - Composition
> // Returns nil if path.rootType != self.valueType
> func appending(path: AnyKeyPath) -> AnyKeyPath?
>
> // MARK - Runtime Information
> class var rootType: Any.Type
> class var valueType: Any.Type
>
> static func == (lhs: AnyKeyPath, rhs: AnyKeyPath) -> Bool
> var hashValue: Int
> }
> Unknown Path / Known Root Type
>
> If we know a little more type information (what kind of thing the key path is
> relative to), then we can use PartialKeyPath <Root>, which refers to an 'any
> route' from a known root:
>
> Swift
> class PartialKeyPath<Root>: AnyKeyPath {
> // MARK - Composition
> // Returns nil if Value != self.valueType
> func appending(path: AnyKeyPath) -> PartialKeyPath<Root>?
> func appending<Value, AppendedValue>(path: KeyPath<Value, AppendedValue>)
> -> KeyPath<Root, AppendedValue>?
> func appending<Value, AppendedValue>(path: ReferenceKeyPath<Value,
> AppendedValue>) -> ReferenceKeyPath<Root, AppendedValue>?
> }
> Known Path / Known Root Type
>
> When we know both what the path is relative to and what it refers to, we can
> use KeyPath. Thanks to the knowledge of the Root and Value types, all of the
> failable operations lose their Optional.
>
> Swift
> public class KeyPath<Root, Value>: PartialKeyPath<Root> {
> // MARK - Composition
> func appending<AppendedValue>(path: KeyPath<Value, AppendedValue>) ->
> KeyPath<Root, AppendedValue>
> func appending<AppendedValue>(path: WritableKeyPath<Value,
> AppendedValue>) -> Self
> func appending<AppendedValue>(path: ReferenceWritableKeyPath<Value,
> AppendedValue>) -> ReferenceWritableKeyPath<Root, AppendedValue>
> }
> Value/Reference Mutation Semantics Mutation
>
> Finally, we have a pair of subclasses encapsulating value/reference mutation
> semantics. These have to be distinct because mutating a copy of a value is
> not very useful, so we need to mutate an inout value.
>
> Swift
> class WritableKeyPath<Root, Value>: KeyPath<Root, Value> {
> // MARK - Composition
> func appending<AppendedPathValue>(path: WritableKeyPath<Value,
> AppendedPathValue>) -> WritableKeyPath<Root, AppendedPathValue>
> }
>
> class ReferenceWritableKeyPath<Root, Value>: WritableKeyPath<Root, Value> {
> override func appending<AppendedPathValue>(path: WritableKeyPath<Value,
> AppendedPathValue>) -> ReferenceWritableKeyPath<Root, AppendedPathValue>
> }
> Access and Mutation Through KeyPaths
>
> To get or set values for a given root and key path we effectively add the
> following subscripts to all Swift types.
>
> Swift
> extension Any {
> subscript(path: AnyKeyPath) -> Any? { get }
> subscript<Root: Self>(path: PartialKeyPath<Root>) -> Any { get }
> subscript<Root: Self, Value>(path: KeyPath<Root, Value>) -> Value { get }
> subscript<Root: Self, Value>(path: WritableKeyPath<Root, Value>) -> Value
> { set, get }
> }
> This allows for code like
>
> Swift
> person[.name] // Self.type is inferred
> which is both appealingly readable, and doesn't require read-modify-write
> copies (subscripts access self inout). Conflicts with existing subscripts are
> avoided by using generic subscripts to specifically only accept key paths
> with a Root of the type in question.
>
> Referencing Key Paths
>
> Forming a KeyPath borrows from the same syntax used to reference methods and
> initializers,Type.instanceMethod only now working for properties and
> collections. Optionals are handled via optional-chaining. Multiply dotted
> expressions are allowed as well, and work just as if they were composed via
> the appending methods on KeyPath.
>
> There is no change or interaction with the #keyPath() syntax introduced in
> Swift 3.
>
> Performance
>
> The performance of interacting with a property via KeyPaths should be close
> to the cost of calling the property directly.
>
> Source compatibility
> This change is additive and there should no affect on existing source.
>
> Effect on ABI stability
> This feature adds the following requirements to ABI stability:
>
> mechanism to access key paths of public properties
> We think a protocol-based design would be preferable once the language has
> sufficient support for generalized existentials to make that ergonomic. By
> keeping the class hierarchy closed and the concrete implementations private
> to the implementation it should be tractable to provide compatibility with an
> open protocol-based design in the future.
>
> Effect on API resilience
> This should not significantly impact API resilience, as it merely provides a
> new mechanism for operating on existing APIs.
>
> Alternatives considered
> More Features
>
> Various drafts of this proposal have included additional features
> (decomposable key paths, prefix comparisons, support for custom KeyPath
> subclasses, creating a KeyPath from a String at runtime, KeyPaths conforming
> to Codable, bound key paths as a concrete type, etc.). We anticipate
> approaching these enhancements additively once the core KeyPath functionality
> is in place.
>
> Spelling
>
> We also explored many different spellings, each with different strengths. We
> have chosen the current syntax due to the balance with existing function type
> references.
>
> Current #keyPath Lisp-style
> Person.friends[0].name #keyPath(Person, .friends[0].name)
> `Person.friend.name
> luke[.friends[0].name] #keyPath(luke, .friends[0].name)
> luke`.friends[0].name
> luke.friends[0][.name] #keyPath(luke.friends[0], .name)
> luke.friends[0]`.name
> While the crispness is very appealing, the spelling of the 'escape' character
> was hard to agree upon (along with the fact that it requires parentheses to
> reduce ambiguity). #keyPath was very specific, but verbose especially when
> composing multiple key paths together.
>
> _______________________________________________
> swift-evolution mailing list
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution