<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div><span></span></div><div><div><span></span></div><div><meta http-equiv="content-type" content="text/html; charset=utf-8"><div><span></span></div><div><meta http-equiv="content-type" content="text/html; charset=utf-8"><div><span></span></div><div><meta http-equiv="content-type" content="text/html; charset=utf-8"><div><span></span></div><div><meta http-equiv="content-type" content="text/html; charset=utf-8"><div><span></span></div><div><div><span></span></div><div><div></div><div><div><span style="background-color: rgba(255, 255, 255, 0);">I concur with Logan’s idea here on the general points, but let me add a bit more. </span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><b><u>Here are some KeyPathy things I’d like to see in a future Swift:</u></b></span></div></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><div><span style="background-color: rgba(255, 255, 255, 0);">/// A set of PartialKeyPath<Type> guaranteed as:</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (a) the entire set of keypaths for a type; and</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (b) accessible given the current scope</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">Type.allKeyPaths() throws -> [PartialKeyPath<Type>]</span></div></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// A set of PartialKeyPath<Type></span><span style="background-color: rgba(255, 255, 255, 0);"> guaranteed as:</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (a) sufficient for initialization of an instance; and </span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (b) accessible given the current scope</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">Type.sufficientPartialKeyPaths() throws -> [PartialKeyPath<Type>]</span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">class Property<KeyPath<RootType,ValueType>> { </span></div><div><span style="background-color: rgba(255, 255, 255, 0);"> let keyPath: KeyPath<RootType, ValueType></span></div><div><span style="background-color: rgba(255, 255, 255, 0);"> let value: ValueType</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">}</span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">Type.init(with properties: [PartialProperty<Type>]) </span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">Type.init(copy: Type, overwriting properties: [PartialProperty<Type>])</span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><div>The idea is a type<span style="background-color: rgba(255, 255, 255, 0);"> can provide you a set of PartialKeyPath<Type> that is guaranteed</span><span style="background-color: rgba(255, 255, 255, 0);"> as sufficient for initialization of an instance of the type, as long as the</span><span style="background-color: rgba(255, 255, 255, 0);"> current scope lets you access it. </span></div></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><b><u>What would also be nice:</u></b></span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><div><div><span style="background-color: rgba(255, 255, 255, 0);">/// A set of PartialKeyPath<Type> guaranteed as</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (a) the entire set of writable keypaths of Type; and</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">/// (b) accessible given the current scope</span></div><div><span style="background-color: rgba(255, 255, 255, 0);">AllWritableKeyPaths<Type, Element></span></div></div></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div>(etc.) :D</div><div><br></div><div><span style="background-color: rgba(255, 255, 255, 0);">Note: in Swift 3.2/4, (of course), AnyKeyPaths and PartialKeyPaths<T> can already be downcast to more specific types like KeyPath<T, E>, WritableKeyPath<T, E>, etc., but only if you already know what T and E are at compile time (i.e. they are not generic). </span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">I have found some bugs though; iterating through arrays of AnyKeyPath using “where” statements to limit the types is a buggy and unpredictable affair (I believe “filter(into:)” works best). </span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div><span style="background-color: rgba(255, 255, 255, 0);">E.g.:</span></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div>extension Array where Element == AnyKeyPath {</div><div> func partialKeyPaths<T>() -> [T] {</div><div> return self.filter(into: [PartialKeyPath<T>]()) </div><div> { result, keyPath in</div><div> if let k = keyPath as? PartialKeyPath<T> {</div><div> result.append(k)</div><div> }</div><div> }</div><div> }</div><div>} </div><div><br></div><div><b><u>To what end?</u></b></div><div><span style="background-color: rgba(255, 255, 255, 0);"><br></span></div><div>What we sorely lack in Swift is a way to (failably) init an object from a set of keypaths and values without tons of boilerplate and/or resorting to using string keys etc. </div><div><br></div><div>Worse, right now there is no way to make a copy of an object/struct while mutating it only at one or two keypaths without writing yet more boilerplatey init methods.</div><div><br></div><div>Heck, right now, keypaths can be used for initializing <span style="background-color: rgba(255, 255, 255, 0);">neither </span>immutable instance constants, nor mutable instance variables that lack default initializers. E.g.: self[keyPath: \Foo.bar] = “baz” fails to compile inside an init method, because the property is not initialized yet. Gee. </div><div><br></div><div><b><u>Towards Type-Safe Instance Composition Patterns:</u></b></div><div><br></div><div>In a type-safe language we can’t have ECMA6-style destructuring (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment</a>)... and please don’t accuse me of wanting it.</div><div><br></div><div>All I want is some sugar that makes the Swift compiler infer more different kinds of convenience init methods. Something like:</div><div><br></div><div>struct Foo {</div><div> let bar: String</div><div> let baz: String</div><div> let leadScientist: QuantumPhysicist</div><div> let <span style="background-color: rgba(255, 255, 255, 0);">labTech</span>: TeleporterTester</div><div>}</div><div><br></div><div>let fooMarch = Foo(bar: “asdf”, baz: “qwer”, leadScientist: QuantumPhysicist(“Alice”), labTech: TeleporterTester(“Bob”))</div><div><br></div><div>let fooApril = Foo(copy: fooMarch, overwriting: Property(\.labTech, <span style="background-color: rgba(255, 255, 255, 0);">TeleporterTester(“Charlie”)</span>) </div><div><br></div><div>... with “overwriting” taking 0 or more variadic arguments.</div><div><br></div><div>This allows easily, concisely composing an immutable instance of a type out of various components of other immutable instances of a type. I think this is an extremely powerful pattern, and many times I wish that I had it. </div><div><br></div><div>In the absence of this, devs are prone to just use mutable instance vars instead of using immutable instance constants, just so they don’t have to do a whole member-wise initializer every time they want to just change one property.</div><div><br></div><div>Just my $0.02.</div><div><br></div><div>If there is already a way to use these things like that, then I want to know it.</div><div><br></div><div><span style="background-color: rgba(255, 255, 255, 0);">As for “why would this really be useful”, “what are the real-world benefits”, etc. ... I feel like if you really have to ask this, then it’s not because you actually cannot see the obvious benefits—it’s because you hate America. </span></div><div><br></div><div>~ Jon Gilbert</div><div><br></div><div>On Aug 23, 2017, at 23:19, Logan Shire via swift-evolution <<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>> wrote:<br><br></div><blockquote type="cite"><div><span>Hey folks! </span><br><span></span><br><span>Recently I’ve been working on a small library which leverages the Swift 4 Codable protocol</span><br><span>and KeyPaths to provide a Swift-y interface to CoreData. (It maps back and forth between</span><br><span>native, immutable Swift structs and NSManagedObjects). In doing so I found a couple of </span><br><span>frustrating limitations to the KeyPath API. Firstly, KeyPath does not provide the name of the </span><br><span>property on the type it indexes. For example, if I have a struct:</span><br><span></span><br><span></span><br><span>struct Person {</span><br><span> let firstName: String</span><br><span> let lastName: String</span><br><span>}</span><br><span></span><br><span>let keyPath = \Person.firstName</span><br><span></span><br><span></span><br><span>But once I have a keyPath, I can’t actually figure out what property it accesses.</span><br><span>So, I wind up having to make a wrapper:</span><br><span></span><br><span></span><br><span>struct Attribute {</span><br><span> let keyPath: AnyKeyPath</span><br><span> let propertyName: String</span><br><span>}</span><br><span></span><br><span>let firstNameAttribute = Attribute(keyPath: \Person.firstName, propertyName: “firstName”)</span><br><span></span><br><span></span><br><span>This forces me to write out the property name myself as a string which is very error prone.</span><br><span>All I want is to be able to access:</span><br><span></span><br><span></span><br><span>keyPath.propertyName // “firstName”</span><br><span></span><br><span></span><br><span>It would also be nice if we provided the full path as a string as well:</span><br><span></span><br><span></span><br><span>keyPath.fullPath // “Person.firstName"</span><br><span></span><br><span></span><br><span>Also, if I want to get all of the attributes from a given Swift type, my options are to try to hack</span><br><span>something together with Mirrors, or forcing the type to declare a function / computed property</span><br><span>returning an array of all of its key path / property name pairings. I would really like to be able to </span><br><span>retrieve a type-erased array of any type’s key paths with:</span><br><span></span><br><span></span><br><span>let person = Person(firstName: “John”, lastName: “Doe”)</span><br><span>let keyPaths = Person.keyPaths</span><br><span>let firstNameKeyPath = keyPaths.first { $0.propertyName = “firstName” } as! KeyPath<Person, String></span><br><span>let firstName = person[keypath: firstNameKeyPath] // “John"</span><br><span></span><br><span></span><br><span>And finally, without straying too far into Objective-C land, it would be nice if we could initialize key paths</span><br><span>with a throwing initializer.</span><br><span></span><br><span></span><br><span>let keyPath = try Person.keyPath(“firstName”) // KeyPath<Person, String> type erased to AnyKeyPath</span><br><span>let keyPath = AnyKeyPath(“Person.firstName”)</span><br><span></span><br><span></span><br><span>Let me know what you think about any / all of these suggestions!</span><br><span></span><br><span></span><br><span>Thanks,</span><br><span>Logan</span><br><span></span><br><span></span><br><span>_______________________________________________</span><br><span>swift-evolution mailing list</span><br><span><a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a></span><br><span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span><br></div></blockquote></div></div></div></div></div></div></div></body></html>