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
