I concur with Logan’s idea here on the general points, but let me add a bit 
more. 

Here are some KeyPathy things I’d like to see in a future Swift:

/// A set of PartialKeyPath<Type> guaranteed as:
/// (a) the entire set of keypaths for a type; and
/// (b) accessible given the current scope
Type.allKeyPaths() throws -> [PartialKeyPath<Type>]

/// A set of PartialKeyPath<Type> guaranteed as:
/// (a) sufficient for initialization of an instance; and 
/// (b) accessible given the current scope
Type.sufficientPartialKeyPaths() throws -> [PartialKeyPath<Type>]

class Property<KeyPath<RootType,ValueType>> {   
    let keyPath: KeyPath<RootType, ValueType>
    let value: ValueType
}

Type.init(with properties: [PartialProperty<Type>]) 

Type.init(copy: Type, overwriting properties: [PartialProperty<Type>])

The idea is a type can provide you a set of PartialKeyPath<Type> that is 
guaranteed as sufficient for initialization of an instance of the type, as long 
as the current scope lets you access it. 

What would also be nice:

/// A set of PartialKeyPath<Type> guaranteed as
/// (a) the entire set of writable keypaths of Type; and
/// (b) accessible given the current scope
AllWritableKeyPaths<Type, Element>

(etc.) :D

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). 

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). 

E.g.:

extension Array where Element == AnyKeyPath {
    func partialKeyPaths<T>() -> [T] {
        return self.filter(into: [PartialKeyPath<T>]()) 
        { result, keyPath in
            if let k = keyPath as? PartialKeyPath<T> {
                result.append(k)
            }
        }
    }
} 

To what end?

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. 

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.

Heck, right now, keypaths can be used for initializing neither 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. 

Towards Type-Safe Instance Composition Patterns:

In a type-safe language we can’t have ECMA6-style destructuring 
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)...
 and please don’t accuse me of wanting it.

All I want is some sugar that makes the Swift compiler infer more different 
kinds of convenience init methods. Something like:

struct Foo {
    let bar: String
    let baz: String
    let leadScientist: QuantumPhysicist
    let labTech: TeleporterTester
}

let fooMarch = Foo(bar: “asdf”, baz: “qwer”, leadScientist: 
QuantumPhysicist(“Alice”), labTech: TeleporterTester(“Bob”))

let fooApril = Foo(copy: fooMarch, overwriting: Property(\.labTech, 
TeleporterTester(“Charlie”)) 

... with “overwriting” taking 0 or more variadic arguments.

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. 

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.

Just my $0.02.

If there is already a way to use these things like that, then I want to know it.

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. 

~ Jon Gilbert

> On Aug 23, 2017, at 23:19, Logan Shire via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Hey folks! 
> 
> Recently I’ve been working on a small library which leverages the Swift 4 
> Codable protocol
> and KeyPaths to provide a Swift-y interface to CoreData. (It maps back and 
> forth between
> native, immutable Swift structs and NSManagedObjects). In doing so I found a 
> couple of 
> frustrating limitations to the KeyPath API. Firstly, KeyPath does not provide 
> the name of the 
> property on the type it indexes. For example, if I have a struct:
> 
> 
> struct Person {
>    let firstName: String
>    let lastName: String
> }
> 
> let keyPath = \Person.firstName
> 
> 
> But once I have a keyPath, I can’t actually figure out what property it 
> accesses.
> So, I wind up having to make a wrapper:
> 
> 
> struct Attribute {
>    let keyPath: AnyKeyPath
>    let propertyName: String
> }
> 
> let firstNameAttribute = Attribute(keyPath: \Person.firstName, propertyName: 
> “firstName”)
> 
> 
> This forces me to write out the property name myself as a string which is 
> very error prone.
> All I want is to be able to access:
> 
> 
> keyPath.propertyName // “firstName”
> 
> 
> It would also be nice if we provided the full path as a string as well:
> 
> 
> keyPath.fullPath // “Person.firstName"
> 
> 
> Also, if I want to get all of the attributes from a given Swift type, my 
> options are to try to hack
> something together with Mirrors, or forcing the type to declare a function / 
> computed property
> returning an array of all of its key path / property name pairings. I would 
> really like to be able to 
> retrieve a type-erased array of any type’s key paths with:
> 
> 
> let person = Person(firstName: “John”, lastName: “Doe”)
> let keyPaths = Person.keyPaths
> let firstNameKeyPath = keyPaths.first { $0.propertyName = “firstName” } as! 
> KeyPath<Person, String>
> let firstName = person[keypath: firstNameKeyPath] // “John"
> 
> 
> And finally, without straying too far into Objective-C land, it would be nice 
> if we could initialize key paths
> with a throwing initializer.
> 
> 
> let keyPath = try Person.keyPath(“firstName”) // KeyPath<Person, String> type 
> erased to AnyKeyPath
> let keyPath = AnyKeyPath(“Person.firstName”)
> 
> 
> Let me know what you think about any / all of these suggestions!
> 
> 
> Thanks,
> Logan
> 
> 
> _______________________________________________
> 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

Reply via email to