[swift-evolution] Smart KeyPaths

Matthew Johnson matthew at anandabits.com
Wed Mar 22 13:27:46 CDT 2017


> On Mar 22, 2017, at 12:34 PM, Vladimir.S <svabox at gmail.com> wrote:
> 
> On 22.03.2017 19:25, Matthew Johnson wrote:
>> 
>>> On Mar 22, 2017, at 11:00 AM, Vladimir.S <svabox at gmail.com
>>> <mailto:svabox at gmail.com>> wrote:
>>> 
>>> On 22.03.2017 18:47, Matthew Johnson wrote:
>>>> 
>>>>> On Mar 22, 2017, at 10:36 AM, Vladimir.S via swift-evolution
>>>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>> On 22.03.2017 17:37, Ricardo Parada wrote:
>>>>>> 
>>>>>> 
>>>>>>> On Mar 22, 2017, at 9:30 AM, Vladimir.S <svabox at gmail.com
>>>>>>> <mailto:svabox at gmail.com>
>>>>>>> <mailto:svabox at gmail.com>> wrote:
>>>>>>> 
>>>>>>> let path = @Bag.things[0].name
>>>>>>> 
>>>>>>> bag at path
>>>>>>> bag at .things[0].name
>>>>>>> bag at Bag.things <mailto:bag at Bag.things> <mailto:bag at Bag.things>[0].name
>>>>>>> bag.things[0]@.name
>>>>>>> bag.things[0]@Thing.name
>>>>>> 
>>>>>> It sounds like the @ character is serving two different purposes which
>>>>>> confused me at first.
>>>>>> 
>>>>>> If I understood correctly, you are using it to get the key path but also
>>>>>> to apply the key path to the bag struct and get the corresponding value.
>>>>>> 
>>>>> 
>>>>> Yes. And the initial proposal suggest the following syntax accordingly:
>>>>> 
>>>>> let path = Bag.things[0].name
>>>>> bag[path]
>>>>> bag[.things[0].name]
>>>>> bag[Bag.things[0].name]
>>>>> bag.things[0][.name]
>>>>> bag.things[0][Thing.name]
>>>> 
>>>> # makes a lot more sense than @ as a sigil.  It follows from #selector and
>>>> #keyPath.  These are the most similar language features right now where the
>>>> compiler produces special values.  I think it’s also worth noticing that
>>>> values produced by #selector and #keyPath are /used/ in normal ways.  There
>>>> is no magic syntax for their use, just a typed value.  If we’re going to
>>>> make a change we should use # instead of `.` for accessing these special
>>>> values but we should stick with subscript for use.
>>> 
>>> Could you clarify, what do you suggest? Something like this:
>>> let path = Bag#things[0]#name
>> 
>> I would only use one # at the start of the key path.  Dots could be used
>> afterwords like this: `Bag#things[0].name`  I think it is important to use
>> normal expression syntax after the key path is introduced.
>> 
> 
> Generally agree.
> 
>>> bag[#path]
>> 
>> No, you would just say `bag[path]`.  `path` is a normal value and the
>> subscript taking a path is a normal subscript.
> 
> That was my main intention - to discuss if we should add some special marker when we create *and* also use key path. To remove confusion when you see 'instance[something]' in one's code and can't be sure if we access a "normal" subscript or 'something' is a key path. Especially if instance is array/dictionary or other collection.
> I believe this feature deserves some highlighting with special syntax even on usage site.
> And subscript in this case IMO is not "normal" subscript - but "special" subscript generated by compiler to work with key path.

The fact that this is a key path exists in the type system and is readily available.  It also likely exists in the variable name.  We don’t use special syntax when we work with a selector or a KVC key path.  I don’t think we should use special syntax here either.

The compiler will synthesizes these subscripts but it also synthesizes other code for us now and will synthesize even more in the future.  I don’t see compiler synthesis as a compelling reason for using special syntax.  The reason it makes some sense for creating the values is because they are values of a special type that only the compiler can create.

One option would be to include `get` and `set` methods on the key path types.  That would allow us to write the subscripts in the standard library (if it is allowed to extend Any) and keep all of the magic in the key path types themselves.  I think I would like that approach.

> 
> 
> Vladimir.
> 
>> 
>>> bag[#things[0]#name]
>> 
>> Here we have a type context expecting a key path with a root of the type of
>> `bag`.  There is no potential ambiguity involved in using the `.` here
>> unless people extend the key path types with static members themselves.  I
>> think it’s fair to say do that at your own risk.  So there is no *need* to
>> use special synatx - dot shorthand would work just fine with no ambiguity
>> problem.  You could imagine the compiler synthesizing static members on key
>> path types like this:
>> 
>> extension PartialKeyPath where Root == BagType {
>>    static var things: KeyPath<Root, ThingsType>  // or WriteableKeyPath
>> or ReferenceWritableKeyPath
>> }
>> 
>> You just say: `bag[.things[0]#name]` using the existing dot shorthand for
>> static members that return a value matching the type they are declared on.
>> 
>> On the other hand, it would be more consistent to introduce # shorthand
>> here and not have the imaginary / synthesized static members on the key
>> path types.  I’m neutral, leaning towards using # shorthand for this.
>> 
>> 
>>> bag[Bag#things[0]#name]
>> 
>> As above, there is no need for a second `#`.  Once the expression has
>> produced a key path all subsequent chained accesses will also produce a key
>> path.
>> 
>> bag[Bag#things[0].name]
>> 
>>> bag.things[0][#name]
>> 
>> As above, here we have a type context in the subscript that expects a key
>> path.  We could use the existing dot shorthand and compiler synthesized
>> static properties on key path types or just introduce a # shorthand.  The
>> latter is probably better for consistency.
>> 
>>> bag.things[0][Thing#name]
>> 
>> Sure, if you don’t like the shorthand.
>> 
>>> 
>>> ,and so
>>> let ref = Bag#foo()
>> 
>> Yep.
>> 
>> One interesting thing to note is that we could also get deep references to
>> unbound methods.  This would effectively combine key paths with unbound
>> method references:
>> 
>> let doSomething = Bag#things[0].doSomething()
>> 
>> used like this:
>> doSomthing(bag)
>> 
>> Using # for key paths also allows us to build on it in the future for
>> collection operators:
>> 
>> Bag#things[#sum].value //  a key path that sums the value of all things in
>> a bag
>> 
>> Reserving this potential is one important reason to only use # where you
>> are introducing a special key path expression and not everywhere in the key
>> path chain.
>> 
>>> 
>>> ?
>>> 
>>> In this case I feel like the following will be more clean syntax:
>>> let path = #Bag.things[0].name
>>> bag[#path]
>>> bag[#.things[0].name]
>>> bag[#Bag.things[0].name]
>>> bag.things[0][#.name]
>>> bag.things[0][#Thing.name]
>>> let ref = #Bag.foo()
>> 
>> This is kind of weird because Bag doesn’t have a static things property.  I
>> think it’s better to start put the # in the middle.  That said, this would
>> work as well.
>> 
>>> 
>>> And why subscript is the only good candidate for use?
>> 
>> It is how Swift models parameterized value access.  Building on that model
>> makes a ton of sense.  I can’t imagine a compelling argument for
>> /using/ key path values.
>> 
>>> Actually, for me personally, any solution will be good as soon as it
>>> contains some 'marker' which saying "hey, here key paths are used. be aware."
>>> 
>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> 
>> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170322/7792fdd4/attachment.html>


More information about the swift-evolution mailing list