on Tue Jul 11 2017, Robert Bennett <[email protected]> wrote:

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

Because they're parameterized by a base object and a key, and (in
general) they're writable, they're semantically very much like
subscripts.

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

-- 
-Dave

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

Reply via email to