Er, yes, I now realize I diverged into two different keypaths-as-functions 
ideas there.

I think that the best implementation of deferred access is keypaths as callable 
first-class objects, like you (Karl) said. — although I wonder whether 
callability should be restricted to KeyPath, or instead, if the notion of a 
callable type should gain first-class language support.

If not possible, then a conversion sigil to make a KeyPath into a function. 
After that, giving KeyPath a function `apply` is probably next best.

I like the subscript idea the least because: I don’t like the look of the 
syntax, keypaths feel more function-y than subscript-y, and it diminishes the 
flexibility of keypaths (as this thread has revealed).

> On Jul 11, 2017, at 7:56 PM, Karl Wagner <[email protected]> wrote:
> 
> 
>> On 12. Jul 2017, at 01:20, Robert Bennett <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>> Well, if they really are first-class deferred method calls or member 
>> accesses, then do seem pretty function-y after all. Then again, if they were 
>> meant to be functions, it seems like their design would reflect that – 
>> instead of being used like subscripts, they would be called like functions, 
>> and since new syntax had to be created either way, the fact that they 
>> *weren't* just made into callable objects seems to indicate that that was 
>> not the intent, although I’d have to go back and read the discussion to see 
>> exactly what was discussed. 
> 
> I agree, and I suspect I’m not alone in disliking the subscript syntax.
> 
>> 
>> That said, I agree with Benjamin that having an `apply` method for KeyPath 
>> seems like the right way to make (or have made) keypaths work. 
>> keypath.apply(to: instance) (or keypath.evaluate(on:), or some other name 
>> that gets the idea across) reads just as nice as instance[keyPath: keypath] 
>> and has the added benefit of allowing collection.map(keypath.apply) at no 
>> cost. But it’s probably too late to even bother having a discussion about 
>> this, right?
> 
> I don’t think so. That’s why we have a beta. Real-world experience has shown 
> that we would often like to erase a KeyPath and use it as if it were a 
> closure. Maybe we want to pass a KeyPath as a parameter to a function such as 
> “map", which accepts a closure, or we want to set a variable with a 
> closure-type using a KeyPath. Those functions and stored properties don’t 
> care about the special properties of KeyPaths (e.g. that they are Codable) - 
> they only care that they are executable on a base object to produce a result. 
> You can wrap the key-paths inside closures, but it’s cumbersome and some 
> developers are asking for a shorthand to perform that erasure.
> 
> Personally, I would be in favour of making the erasure implicit and allowing 
> KeyPaths to be invoked using function-syntax:
> 
> // Invocation using function-syntax:
> 
> let _: Value = someClosure(parameter)
> let _: Value = someKeypath(parameter)
> 
> let _: Value = { $0.something.anotherThing }(parameter)
> let _: Value = (\MyObj.something.anotherThing)(parameter)
> 
> // Implicit erasure to closure-type:
> 
> class PrettyTableCell<T> {
>     let titleFormatter: (T) -> String
> }
> let cell            = PrettyTableCell<MyObj>()
> cell.titleFormatter = \MyObj.something.name
> 
> let stuff = myObjects.map(\.something.anotherThing)
> 
> - Karl
> 
>> 
>>> On Jul 11, 2017, at 6:27 PM, Karl Wagner <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> 
>>>> On 11. Jul 2017, at 21:01, Robert Bennett via swift-evolution 
>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>> 
>>>> In general, I like the idea of making ordinary types callable (although 
>>>> curried functions already accomplish this to some extent), but I hesitate 
>>>> to bring this capability to keypaths because, well, they don’t really feel 
>>>> like functions; I think the point of them is that they work like 
>>>> subscripts, not functions. After all, before keypaths were added, there 
>>>> was already an easy to make a function that does what a keypath does 
>>>> (which makes me wonder whether keypaths were necessary in the first place, 
>>>> but that ship has sailed). The only reason to add callable support to 
>>>> keypaths is for use in map, which I don’t think justifies making them 
>>>> callable.
>>>> 
>>>> Also, since I brought this up, I’d like to be proved wrong about keypaths 
>>>> – what use do they have that isn’t accomplished by the equivalent closure?
>>> 
>>> I can’t find a formal definition of a “keypath”, so let me explain how I 
>>> think of them:
>>> 
>>> Conceptually, I think I would define a KeyPath as a stateless, deferred 
>>> function application with one unbound argument (the “base"). Anything you 
>>> do with a KeyPath could be done with a closure of type (Base)->Value which 
>>> captures all other arguments (e.g. subscript/function parameters). The 
>>> major benefit that it has over a closure is identity (so you can put it in 
>>> a dictionary or compare two keypaths), and that property that captures all 
>>> of its parameters except the base, and that those parameters don’t have 
>>> stateful side-effects. That makes it really handy for parallel execution 
>>> and database predicates in ORMs.
>>> 
>>> There’s also another benefit of KeyPaths: they are de-/serialisable. Again, 
>>> since it captures all of its (stateless) parameters, it itself is stateless 
>>> and can be transferred to persistent storage or over a network.
>>> 
>>> You can actually see those constraints in the KeyPath proposal (which is 
>>> part of what makes it such a great proposal, IMO): all captured parameters 
>>> must be Hashable and Codable.
>>> 
>>> But to come back to your point - in all other respects a KeyPath is 
>>> conceptually identical to a closure of type (Base)->Value. It’s like a 
>>> specially-annotated closure, where it’s special construction syntax lets us 
>>> statically verify that it’s a stateless, deferred function applicable to an 
>>> instance of the Base type.
>>> 
>>> The KeyPath proposal said that eventually, the core team would like to be 
>>> able to support arbitrary function calls in KeyPath expressions, too. For 
>>> example, it’s "not fair” that \MyObject.firstFiveElements and \MyObject[3] 
>>> are valid KeyPaths, but \MyObject.prefix(5) is not. It’s also expressible 
>>> as (Base)->Value, so conceptually it’s also a KeyPath and can be serialised 
>>> and whatnot.
>>> 
>>> - Karl 
>>> 
>>>>> On Jul 11, 2017, at 2:28 PM, Benjamin Herzog via swift-evolution 
>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>> 
>>>>> I still think using an operator for this conversation would neither 
>>>>> increase readability nor transparency. I think my mail on Sunday was 
>>>>> lost, so I paste the content here again. It referred to a suggestion to 
>>>>> create a possibility for KeyPath to act as a function which would bring 
>>>>> other benefits as well:
>>>>> 
>>>>> In Scala you can implement an apply method which makes it possible to 
>>>>> call an object just like a function. Example:
>>>>> 
>>>>> case class Foo(x: Int) {
>>>>>  def apply(y: Int) = x + y
>>>>> }
>>>>> 
>>>>> val foo = Foo(3)
>>>>> val bar = foo(4) // 7
>>>>> 
>>>>> That is similar to what you suggested to have a possibility to convert an 
>>>>> object to a closure getting called. And I totally see the point for this! 
>>>>> I think using a keyword or special name like apply is not a good idea 
>>>>> because it's not obvious what it does and it also makes it possible to 
>>>>> just call the method with its name: foo.apply(4).
>>>>> 
>>>>> However, having a protocol is kinda hard because it's not possible to 
>>>>> have a flexible parameter list. Maybe having a method without a name? 
>>>>> Swift example:
>>>>> 
>>>>> class Foo {
>>>>>  var x: Int
>>>>>  init(x: Int) { self.x = x }
>>>>> 
>>>>>  func (y: Int) -> Int {
>>>>>      return self.x + y
>>>>>  }
>>>>> }
>>>>> 
>>>>> let foo = Foo(x: 3)
>>>>> let bar = foo(y: 4) // 7
>>>>> 
>>>>> I actually like that, would be like an anonymous function. It would also 
>>>>> be possible to have multiple of those defined for one object (which would 
>>>>> have to be unambiguous of course).
>>>>> 
>>>>> So getting back to KeyPath, it could look like this:
>>>>> 
>>>>> class KeyPath<Root, Value> {
>>>>>  func (_ root: Root) -> Value {
>>>>>      return root[keyPath: self]
>>>>>  }  
>>>>> }
>>>>> 
>>>>> I see that this would be a much bigger change and would not justify the 
>>>>> syntactic sugar for map, flatMap, etc. But it would still be a nice 
>>>>> addition to the Swift programming language, especially for KeyPath, 
>>>>> transformers etc.
>>>>> 
>>>>> What do you think?
>>>>> 
>>>>> ______________________
>>>>> 
>>>>> Benjamin Herzog
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> [email protected] <mailto:[email protected]>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> [email protected] <mailto:[email protected]>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> 
> 

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to