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
