[Pitch] KeyPath based map, flatMap, filter

I agree with Brent (and others) that this is sugar worth having. If keypaths had been introduced earlier in the language’s evolution, array.map { $0.property } would look as ugly, and as Brent put it, as noisy, as array.map { f($0) }; we only accept it because we’ve had no better alternative. It feels as clunky to use a function to extract a property from a mapped element as it does to construct a closure whose only purpose is to call another function. If array.map(f) is allowed, then array.map(\.property) should be as well. It’s only natural.

That said, this addition does not necessarily need to occur soon. Xiaodi is right that once this is added, we’re pretty much stuck with it. Perhaps only after an evaluation of how keypaths are used by the Swift community should this addition to the language be reconsidered. There’s no need to permanently shelve the idea now, though. You get to eat dessert once you’ve eaten all your vegetables.

···

On Jul 6, 2017, at 11:01 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

The lesson I took away from SE-0110 is that conveniences, once offered, are disruptive to take away. People who don't delve deeply into the reasons that make the conveniences unworkable in the grand scheme of things are upset when these features are taken away because specific use cases are made less ergonomic.

Since, as you say, subtyping is always a bit perilous, if this idea is adopted but turns out to have important overlooked problems, trying to roll it back is going to cause a lot of pain. Not offering it in the first place, however, is by comparison only a slight inconvenience. No one has ever died of not drinking alcohol; plenty have died of alcohol withdrawal. Therefore, my lesson from SE-0110 is: vegetables now, vegetables forever.
On Thu, Jul 6, 2017 at 21:38 Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 6, 2017, at 9:13 AM, Dave Abrahams <dabrahams@apple.com> wrote:

I'm not sure what you're objecting to about this. Is it the very
appearance of curly braces?

I went to bed thinking that maybe I should have explained that better, and I guess I was right. ;^) Here's why I think we should do something here.

From what I can tell, mapping or filtering on a single property is exceptionally common. I ran a few regexes on the Swift code present on my machine (basically the stuff in the Swift repos, plus my open-source projects, plus my closed-source stuff, plus various playgrounds and things) to see how common different kinds of `map`, `filter`, and `flatMap` closures were:

  2142 OP { …$0… }
  1835 OP(function) or OP(some.method)
  589 OP { $0.property } or OP { $0.some.property }
  564 OP { $0.property }
  525 OP { function(…$0…) } or OP { some.method(…$0…) }
  186 OP { $0.method(…) }
  153 OP { function($0) } or OP { some.method($0) }
  100 OP { $0 as SomeType } or OP { $0 as? SomeType } or OP { $0 as! SomeType }
  52 OP { $0.method() }
  35 OP { collection[…$0…] } or OP { some.collection[…$0…] }
  20 OP { collection[$0] } or OP { some.collection[$0] }
  13 OP { $0! }

(Simple regex-based match of `map`, `flatMap`, and `filter` calls. Permits various spacing schemes and `try`. If you want to run it on a more representative sample, the script is here, requires fish(1): https://gist.github.com/brentdax/2a8ee2705c39e9948aafedbd81b1366f\)

So, at least in my unscientific sample, more than a quarter of map/filter/flatMap calls which use `$0` simply look up a property or chain of properties on it. If we want to make something about `map` and friends more convenient, this seems like a good place to look.

(Using a straight function is a few times more common than a property, but given that this is also the syntax used to abstract over a closure body, I'm not sure how much weight to put on that fact.)

So what's wrong with what we have now? Syntactic weight. Consider this expression:

  person.map { $0.name }

The "loudest" parts of this expression are the closure brackets and the `$0`, but they are actually the *least* important. They do not express anything about what this line of code *does*; they exist solely to tell the compiler how to do it. They are pure glue code, and serve only to obscure the actual intent. Compare that to:

  person.map(\.name)

Here, we still have a glue character (the `\`), but it's just one, and it's relatively inconspicuous compared to something like `$0`.

That's not *too* bad, though. It gets a lot worse when the key path is actually in a variable:

  array.map { $0[keyPath: prop] }

Again, look at how much of this line is given over to adapting a line of code to the compiler—and how little that actually matters when understanding what the line does. The most important thing in that expression is `prop`, but it's completely lost in this sea of irrelevant syntax. Compare to:

  array.map(prop)

Which puts that piece of information front and center.

If there was an argument that the case was too complex to handle nicely, I think we could justify leaving it alone. That's essentially what happened with the much-requested placeholder syntax: Lots of people wanted it, but critics pointed out the fundamental ambiguity of the syntax, and after spending gallons of electrons arguing about it, the proponents pretty much backed off. But key paths don't have that problem—they always work the same way and are completely unambiguous, so there's no scope for misinterpretation. And the cases key paths can handle appear to be about as common as the cases placeholder syntax was intended for.

Nor is there a strong argument that the suggested behavior is fundamentally "weird". It's pretty natural to think of a `KeyPath<Root, Value>` as being a `(Root) -> Value` function, and the `foo.map(\.bar)` syntax reads pretty straightforwardly as long as you know what `map` does.

There's one more reason I think we should do this. It is not about the technology; it is not even really about the ergonomics. It's more about language "marketing", for lack of a better term.

I think we were all surprised by the SE-0110 backlash. But in hindsight, I think it's pretty easy to explain. During the Swift 3 and 4 cycles, we systematically stripped conveniences and sugar from higher-order functions. We have good reasons to do this; we want to pare things down, fix the foundations, and then build up new conveniences. Eat your vegetables now and you can have dessert later.

But we're entering our second year of eating nothing but vegetables. It's very difficult to maintain a strict diet forever, especially when—like the vast majority of Swift's users who don't participate in evolution or implementation—you don't really see the point of it. It's hard to blame them for being tired of it, or for complaining when yet another tasty food is pulled off the menu.

Offering a treat like this on occasion will help ease the pain of losing the things we *need* to take away. And this is a particularly good candidate because, although it's a convenience for higher-order functions—which is where the pain is felt—it has little to do with parameter handling, the area where we actually need to remove things and refactor. It's like a dessert of ultra-dark chocolate—it's a treat that doesn't set the actual goal back very far.

In the abstract, "fundamentals now, sugar later" is the right approach. But it can't be considered "right" if the users won't accept it. So let's look for opportunities to add conveniences where we can. Maybe this isn't the right feature—subtyping is always a bit perilous—but we should be on the lookout for features like this one, places where we can improve things for our functional programming fans without obstructing our own efforts to clean up parameter handling.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I like this promote operator idea. I have been defining similar operators for specific projects almost at random. It makes sense to come up with a well-defined behavior and name for such operators, as a common practice as you suggest.

The problem with the postfix operator is that it does not currently work without an extra set of parenthesis:

postfix operator ^

postfix func ^<T,U>(lhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: lhs] } }

struct Guy { let name: String }

let guys = [
    Guy(name: "Benjamin"),
    Guy(name: "Dave"),
    Guy(name: "Brent"),
    Guy(name: "Max")
]

guys.map(\.name^) // Error: Invalid component of Swift key path

guys.map((\.name)^) // This works

Is this a bug?

That is the reason I used a prefix operator (~) in my suggestion in the a previous e-mail on this thread.

···

On Jul 7, 2017, at 4:27 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

By the way, if you're worried about whether subtyping will fly, I've
recently been thinking there might be a role for a “promotion” operator
that enables lossless “almost-implicit” conversions, e.g.:

   someNumber^ is equivalent to numericCast(someNumber)
   \.someKeyPath^ is equivalent to { $0\.someKeyPath }
   someSubstring^ is equivalent to String(someSubstring)

   etc.

This convenience can be implemented by anyone today for keypaths, and
will provide nearly the syntax you're looking for. This is exactly the
sort of thing I'd love to see become a widespread existing practice
before we incorporate it in the standard library, so we could properly
judge its impact on real code.

Yes, that's a lot of extra syntax. But again, you've used an
abbreviated, single identifier for the property and a
short, non-descriptive identifier for the array. Let's make this a
fair/realistic comparison:

       gradeHistories.map { $0[keyPath: \.average] }

vs.
       gradeHistories.map(\.average)

FWIW, this example isn't terribly realistic because you'd normally use a direct reference to a property, rather than a key path, in the transform closure. But after correcting for that, I don't think it changes the calculus very much:

  gradeHistories.map { $0[keyPath: aggregate] }
  gradeHistories.map(aggregate)

That's essentially what happened with the much-requested placeholder
syntax:

Sorry, I may have missed that discussion.

It was somewhere back in the Swift 3 timeframe. Short version is, people wanted a way to say something like:

  numbers.map(abs(_) + 1)

But it was pointed out that this is fundamentally unclear about which of these you mean:

  numbers.map({ abs($0) } + 1)
  numbers.map({ abs($0) + 1 })
  { numbers.map(abs($0) + 1) }

And so the proponents pretty much dropped the suggestion.

It's not fundamentally weird. I'm just not sure it's important.

I'm not going to argue key-paths-as-functions are critical, because they're not. But I think they should be on the to-do list. And I think that niceties *like* this are, as a whole, something we should try to deliver more of. The soundness work that's being prioritized right now is really important, so I'm not sure how exactly to manage this—maybe these niceties should be left more to the community while full-time professional contributors focus more on core design issues and deep refactoring. But however we achieve it, I think a spoonful of syntactic sugar would help the medicine go down.

If it
actually is important, as I said, I feel very strongly that it shouldn't
require anyone to create an overload of map, because that quickly leads
to overloading everything that takes a closure.

I agree that we shouldn't overload `map` to specially support keypaths, except perhaps as a stopgap measure while we support subtyping more fully. And I don't think this is important enough to justify a stopgap.

There's one more reason I think we should do this. It is not about the
technology; it is not even really about the ergonomics. It's more
about language "marketing", for lack of a better term.

I think we were all surprised by the SE-0110 backlash. But in
hindsight, I think it's pretty easy to explain. During the Swift 3 and
4 cycles, we systematically stripped conveniences and sugar from
higher-order functions. We have good reasons to do this; we want to
pare things down, fix the foundations, and then build up new
conveniences. Eat your vegetables now and you can have dessert later.

But we're entering our second year of eating nothing but
vegetables. It's very difficult to maintain a strict diet forever,
especially when—like the vast majority of Swift's users who don't
participate in evolution or implementation—you don't really see the
point of it. It's hard to blame them for being tired of it, or for
complaining when yet another tasty food is pulled off the menu.

Offering a treat like this on occasion will help ease the pain of
losing the things we *need* to take away. And this is a particularly
good candidate because, although it's a convenience for higher-order
functions—which is where the pain is felt—it has little to do with
parameter handling, the area where we actually need to remove things
and refactor. It's like a dessert of ultra-dark chocolate—it's a treat
that doesn't set the actual goal back very far.

In the abstract, "fundamentals now, sugar later" is the right
approach. But it can't be considered "right" if the users won't accept
it. So let's look for opportunities to add conveniences where we
can. Maybe this isn't the right feature—subtyping is always a bit
perilous—but we should be on the lookout for features like this one,
places where we can improve things for our functional programming fans
without obstructing our own efforts to clean up parameter handling.

These are all good arguments. For me it's a question of priorities and
long-term, irrevocable impacts.

By the way, if you're worried about whether subtyping will fly, I've
recently been thinking there might be a role for a “promotion” operator
that enables lossless “almost-implicit” conversions, e.g.:

   someNumber^ is equivalent to numericCast(someNumber)
   \.someKeyPath^ is equivalent to { $0\.someKeyPath }
   someSubstring^ is equivalent to String(someSubstring)

   etc.

I actually played with something like this years ago (pre-open source, IIRC), but I used `^` as a prefix operator and made it support only widening conversions. But it was old code, and redoing it nerd-sniped me so hard that I kind of ended up making a whole GitHub project from it: <https://github.com/brentdax/Upconvert&gt;

The main component is an `Upconvertible` protocol which encapsulates the conversion. That works really well in some ways, but it also creates some important limitations:

1. I had trouble incorporating downconversions in a reasonable way. Key paths in particular would require either compiler support or some really hacky, fragile code that opened up the closure context and pulled out the KeyPath object.

2. There's no good way to support more than one upconversion from a single type. (For instance, you can't make `UInt16` upconvert to both `Uint32` and `Int32`.)

3. Even if #2 were somehow fixed, you still can't make all `LosslessStringConvertible` types conform to `Upconvertible`.

4. Can't upconvert from a structural type, of course.

5. I wanted to support passing through any number of valid upconversions with a single `^` operator, but the only way I could find to do that was to overload the operator with a two-step version, a three-step version, etc.

6. Upconverting a `\.keyPath` expression caused an ambiguity error; I had to overload the operator to make it favor `KeyPath`. (Workaround code: https://github.com/brentdax/Upconvert/blob/master/Upconvert/Conformances/KeyPath.swift#L25\)

Several-to-all of these could be avoided with a built-in language feature.

As for the ergonomics…well, `people.map(^\.name)` definitely feels better than the closure alternative. But it's something you have to learn is possible, and even if you knew about `^` in the context of (say) numeric conversions, I'm not sure people would think to try it there. It basically means you need to know about three slightly esoteric features instead of two; I'm not sure people will discover that.

···

On Jul 7, 2017, at 4:27 PM, Dave Abrahams <dabrahams@apple.com> wrote:

--
Brent Royal-Gordon
Architechies

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 Tue Jul 11 2017, Robert Bennett <swift-evolution@swift.org> wrote:

On Jul 11, 2017, at 7:56 PM, Karl Wagner <razielim@gmail.com> wrote:

On 12. Jul 2017, at 01:20, Robert Bennett >>> <rltbennett@icloud.com >>> <mailto:rltbennett@icloud.com>> 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 >>>> <razielim@gmail.com >>>> <mailto:razielim@gmail.com>> wrote:

On 11. Jul 2017, at 21:01, Robert Bennett via swift-evolution >>>>> <swift-evolution@swift.org >>>>> <mailto:swift-evolution@swift.org>> >>>>> 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 <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

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 <razielim@gmail.com> wrote:

On 12. Jul 2017, at 01:20, Robert Bennett <rltbennett@icloud.com <mailto:rltbennett@icloud.com>> 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 <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

On 11. Jul 2017, at 21:01, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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 <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Sure, but you could just as easily call a keypath “a thing that gives you read+write access to one of an object’s members”. Sounds like an inout function to me. The difference being that in general an inout function *can* do a lot more than just give access to a single member of a base object. But a keypath is still just a (constrained) inout function.

···

On Jul 11, 2017, at 9:14 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Tue Jul 11 2017, Robert Bennett <swift-evolution@swift.org> 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 <razielim@gmail.com> wrote:

On 12. Jul 2017, at 01:20, Robert Bennett >>>> <rltbennett@icloud.com >>>> <mailto:rltbennett@icloud.com>> 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 >>>>> <razielim@gmail.com >>>>> <mailto:razielim@gmail.com>> wrote:

On 11. Jul 2017, at 21:01, Robert Bennett via swift-evolution >>>>>> <swift-evolution@swift.org >>>>>> <mailto:swift-evolution@swift.org>> >>>>>> 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 <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Just realized that even inout functions don’t let you do member(object) = value. So you’re right, the write access does make keypaths more like subscripts. But when restricted to read-only access, they are identical to (non-inout) functions and so it does seem natural to be able to use them as such for the purpose of map and similar functions.

You’ve convinced me that the sigil method is probably preferable... Since write access does make keypaths more like subscripts, the proper way to convert keypaths to functions is probably an explicit piece of punctuation.

···

On Jul 11, 2017, at 9:23 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Sure, but you could just as easily call a keypath “a thing that gives you read+write access to one of an object’s members”. Sounds like an inout function to me. The difference being that in general an inout function *can* do a lot more than just give access to a single member of a base object. But a keypath is still just a (constrained) inout function.

On Jul 11, 2017, at 9:14 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Tue Jul 11 2017, Robert Bennett <swift-evolution@swift.org> 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 <razielim@gmail.com> wrote:

On 12. Jul 2017, at 01:20, Robert Bennett >>>>> <rltbennett@icloud.com >>>>> <mailto:rltbennett@icloud.com>> 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 >>>>>> <razielim@gmail.com >>>>>> <mailto:razielim@gmail.com>> wrote:

On 11. Jul 2017, at 21:01, Robert Bennett via swift-evolution >>>>>>> <swift-evolution@swift.org >>>>>>> <mailto:swift-evolution@swift.org>> >>>>>>> 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 <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> 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
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org

<mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The other difference is that an inout function can't be used to get a
member from an immutable value, whereas a keypath/subscript/property
access can.

···

on Tue Jul 11 2017, Robert Bennett <rltbennett-AT-icloud.com> wrote:

Just realized that even inout functions don’t let you do
member(object) = value.

--
-Dave

Is this operator common in other languages? I would actually expect that the conversation is not 'almost-implicit' but completely implicit instead. I think both - a prefix and postfix operator - are not obvious enough what happens here, especially because this kind of conversion is not happening in other parts of the language.
All conversions are implicit (from explicit type to protocol, from Swift stdlib types to Objective-C types, from any type to Any, …) currently.

···

______________________

Benjamin Herzog

On 8. Jul 2017, at 22:10, Hooman Mehr via swift-evolution <swift-evolution@swift.org> wrote:

I like this promote operator idea. I have been defining similar operators for specific projects almost at random. It makes sense to come up with a well-defined behavior and name for such operators, as a common practice as you suggest.

The problem with the postfix operator is that it does not currently work without an extra set of parenthesis:

postfix operator ^

postfix func ^<T,U>(lhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: lhs] } }

struct Guy { let name: String }

let guys = [
    Guy(name: "Benjamin"),
    Guy(name: "Dave"),
    Guy(name: "Brent"),
    Guy(name: "Max")
]

guys.map(\.name^) // Error: Invalid component of Swift key path

guys.map((\.name)^) // This works

Is this a bug?

That is the reason I used a prefix operator (~) in my suggestion in the a previous e-mail on this thread.

But however we achieve it, I think a spoonful of
syntactic sugar would help the medicine go down.

Let me be clear: syntactic sugar matters. Otherwise, we'd all be
programming directly in LLVM IR. It's just a question of what you have
to pay to get it.

By the way, if you're worried about whether subtyping will fly, I've
recently been thinking there might be a role for a “promotion” operator
that enables lossless “almost-implicit” conversions, e.g.:

   someNumber^ is equivalent to numericCast(someNumber)
   \.someKeyPath^ is equivalent to { $0\.someKeyPath }
   someSubstring^ is equivalent to String(someSubstring)

   etc.

I actually played with something like this years ago (pre-open source,
IIRC), but I used `^` as a prefix operator and made it support only
widening conversions. But it was old code, and redoing it nerd-sniped
me so hard that I kind of ended up making a whole GitHub project from
it: <https://github.com/brentdax/Upconvert&gt;

The main component is an `Upconvertible` protocol which encapsulates
the conversion. That works really well in some ways, but it also
creates some important limitations:

1. I had trouble incorporating downconversions in a reasonable
way. Key paths in particular would require either compiler support or
some really hacky, fragile code that opened up the closure context and
pulled out the KeyPath object.

2. There's no good way to support more than one upconversion from a
single type. (For instance, you can't make `UInt16` upconvert to both
`Uint32` and `Int32`.)

3. Even if #2 were somehow fixed, you still can't make all
`LosslessStringConvertible` types conform to `Upconvertible`.

4. Can't upconvert from a structural type, of course.

AFAICT, all of the above come down to having tried to build this idiom
around a protocol with an associated type. I think of it as a
special-case syntactic shortcut for “value-preserving conversion to
deduced type.” Just overload the operator and be done with it. Maybe
protocols like BinaryInteger should have a generic operator, but this
doesn't deserve a protocol of its own.

5. I wanted to support passing through any number of valid
upconversions with a single `^` operator, but the only way I could
find to do that was to overload the operator with a two-step version,
a three-step version, etc.

6. Upconverting a `\.keyPath` expression caused an ambiguity error; I
had to overload the operator to make it favor `KeyPath`. (Workaround
code:
https://github.com/brentdax/Upconvert/blob/master/Upconvert/Conformances/KeyPath.swift#L25\)

Meh; that's a fact of life when overloading.

Several-to-all of these could be avoided with a built-in language feature.

IMO no language feature is needed for this.

As for the ergonomics…well, `people.map(^\.name)` definitely feels
better than the closure alternative. But it's something you have to
learn is possible, and even if you knew about `^` in the context of
(say) numeric conversions, I'm not sure people would think to try it
there. It basically means you need to know about three slightly
esoteric features instead of two; I'm not sure people will discover
that.

Yes, but there's a trade-off between discoverability, and introducing
more implicit conversions, which will slow down the type checker and can
make errors harder to understand. Note: I'm not arguing for either
approach in particular. They're just available alternatives.

···

on Sun Jul 09 2017, Brent Royal-Gordon <brent-AT-architechies.com> wrote:

--
-Dave

If it would be possible to make objects callable, wouldn't that also go
in the same direction as subscripts currently? One could also implement
it with the syntax for callable functions (in this case anonymous -
without a name). Instead of this:

subscript(index: Int) -> T

we could also write

func (_ index: Int) -> T

On the call side it would change from this:

list[3] to list(3)

I know that it's not necessary and not even better readable, but it goes
in the same direction in my opinion and is worth considering. What do
you think?

···

______________________

Benjamin Herzog

On Wed, Jul 12, 2017, at 10:21 PM, Dave Abrahams via swift-evolution wrote:

on Tue Jul 11 2017, Robert Bennett <rltbennett-AT-icloud.com> wrote:

> Just realized that even inout functions don’t let you do
> member(object) = value.

The other difference is that an inout function can't be used to get a
member from an immutable value, whereas a keypath/subscript/property
access can.

--
-Dave
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hmm… I forgot about setters.

I would still like some simple way to use a KeyPath where an equivalent closure would be expected (for getters AND setters). The operator approach looks like the best one, but I’d prefer a prefix operator, so…

\MyObj.something.name // returns KeyPath<MyObj, String>

^\MyObj.something.name // returns (MyObj)->String
^myKeyPath // as above.

*\MyObj.something.name // returns (inout MyObj, String)->Void
*myKeyPath // as above.

Where those operators could, either now or later, be backed by some kind of protocol (similar to Equatable/Comparable) to express general-purpose callable objects.

But I don’t like how cryptic it all looks.

- Karl

···

On 12. Jul 2017, at 22:21, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Tue Jul 11 2017, Robert Bennett <rltbennett-AT-icloud.com> wrote:

Just realized that even inout functions don’t let you do
member(object) = value.

The other difference is that an inout function can't be used to get a
member from an immutable value, whereas a keypath/subscript/property
access can.

--
-Dave
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’m not sure about needing a universal promotion operator in Swift. However, in the case of KeyPaths, I think a leading $ (not currently a valid operator) would work well.

prefix operator $
prefix func $<T,U>(rhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: rhs] } }

guys.map($\.name)

This reads really well to me because the $ is suggestive of the “functionization” of the KeyPath. Also, this is guaranteed to have no compatibility issues (right?) because it’s currently forbidden.

(I’m only suggesting giving the leading $ this functionality, not necessarily achieving this by making it a valid operator — in fact, it would probably be best if this functionality were “hard-coded” just as it is currently hard-coded for use in $0.)

···

On Jul 8, 2017, at 5:46 PM, Benjamin Herzog via swift-evolution <swift-evolution@swift.org> wrote:

Is this operator common in other languages? I would actually expect that the conversation is not 'almost-implicit' but completely implicit instead. I think both - a prefix and postfix operator - are not obvious enough what happens here, especially because this kind of conversion is not happening in other parts of the language.
All conversions are implicit (from explicit type to protocol, from Swift stdlib types to Objective-C types, from any type to Any, …) currently.

______________________

Benjamin Herzog

On 8. Jul 2017, at 22:10, Hooman Mehr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I like this promote operator idea. I have been defining similar operators for specific projects almost at random. It makes sense to come up with a well-defined behavior and name for such operators, as a common practice as you suggest.

The problem with the postfix operator is that it does not currently work without an extra set of parenthesis:

postfix operator ^

postfix func ^<T,U>(lhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: lhs] } }

struct Guy { let name: String }

let guys = [
    Guy(name: "Benjamin"),
    Guy(name: "Dave"),
    Guy(name: "Brent"),
    Guy(name: "Max")
]

guys.map(\.name^) // Error: Invalid component of Swift key path

guys.map((\.name)^) // This works

Is this a bug?

That is the reason I used a prefix operator (~) in my suggestion in the a previous e-mail on this thread.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The $ 'operator' would also be a good option because it matches the $0 syntax which is not far away logically.

···

______________________

Benjamin Herzog

On 9. Jul 2017, at 00:00, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

I’m not sure about needing a universal promotion operator in Swift. However, in the case of KeyPaths, I think a leading $ (not currently a valid operator) would work well.

prefix operator $
prefix func $<T,U>(rhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: rhs] } }

guys.map($\.name)

This reads really well to me because the $ is suggestive of the “functionization” of the KeyPath. Also, this is guaranteed to have no compatibility issues (right?) because it’s currently forbidden.

(I’m only suggesting giving the leading $ this functionality, not necessarily achieving this by making it a valid operator — in fact, it would probably be best if this functionality were “hard-coded” just as it is currently hard-coded for use in $0.)

On Jul 8, 2017, at 5:46 PM, Benjamin Herzog via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Is this operator common in other languages? I would actually expect that the conversation is not 'almost-implicit' but completely implicit instead. I think both - a prefix and postfix operator - are not obvious enough what happens here, especially because this kind of conversion is not happening in other parts of the language.
All conversions are implicit (from explicit type to protocol, from Swift stdlib types to Objective-C types, from any type to Any, …) currently.

______________________

Benjamin Herzog

On 8. Jul 2017, at 22:10, Hooman Mehr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I like this promote operator idea. I have been defining similar operators for specific projects almost at random. It makes sense to come up with a well-defined behavior and name for such operators, as a common practice as you suggest.

The problem with the postfix operator is that it does not currently work without an extra set of parenthesis:

postfix operator ^

postfix func ^<T,U>(lhs: KeyPath<T,U>) -> (T)->U { return { $0[keyPath: lhs] } }

struct Guy { let name: String }

let guys = [
    Guy(name: "Benjamin"),
    Guy(name: "Dave"),
    Guy(name: "Brent"),
    Guy(name: "Max")
]

guys.map(\.name^) // Error: Invalid component of Swift key path

guys.map((\.name)^) // This works

Is this a bug?

That is the reason I used a prefix operator (~) in my suggestion in the a previous e-mail on this thread.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Subscripts in Swift in fact have always looked to me like a way to make objects "callable", where you use brackets instead of parentheses: the difference of course is the fact that, when used point-free, the object will be considered an instance and not a function.

In this sense we could theoretically make a KeyPath callable by adding subscripts; consider the following code:

extension KeyPath {
  subscript (get root: Root) -> Value {
    return root[keyPath: self]
  }
}

struct Person {
  var firstName: String
  var lastName: String
}

let x = Person.init(firstName: "Foo", lastName: "Bar")

let foo = (\Person.firstName)[get: x] /// "Foo"

This is valid Swift code (Swift 4.0 snapshot 2017-07-13). In theory a possible evolution of this could be referencing the subscript function in a point-free way like we can already do with regular functions:

let callable1 = (\Person.firstName)[get:] /// this won't compile

For the setter part, because the other half of a KeyPath is essentially a function of type (Value, inout Root) -> (), we could theoretically considering this kind of subscript function:

extension KeyPath {
  subscript (set value: Value, on root: inout Root) -> () { /// this won't compile
    root[keyPath: self] = value
    return ()
  }
}

let callable2 = (\Person.firstName)[set:,on:] /// this won't compile

But we cannot write subscripts with inout parameters. I actually find the subscript path a very interesting one to consider, but there's still a lot of machinery that's missing.

I would prefer two standardized prefix operators - for extracting the "(Root) -> Value" and the "(Value,inout Root) -> ()" parts of a KeyPath - to be added to the standard library. This is exactly the case where custom operators make sense: repeated operations that should be expressed with minimum code noise.

Elviro

···

Il giorno 16 lug 2017, alle ore 18:34, Benjamin Herzog via swift-evolution <swift-evolution@swift.org> ha scritto:

If it would be possible to make objects callable, wouldn't that also go
in the same direction as subscripts currently? One could also implement
it with the syntax for callable functions (in this case anonymous -
without a name). Instead of this:

subscript(index: Int) -> T

we could also write

func (_ index: Int) -> T

On the call side it would change from this:

list[3] to list(3)

I know that it's not necessary and not even better readable, but it goes
in the same direction in my opinion and is worth considering. What do
you think?

______________________

Benjamin Herzog

On Wed, Jul 12, 2017, at 10:21 PM, Dave Abrahams via swift-evolution > wrote:

on Tue Jul 11 2017, Robert Bennett <rltbennett-AT-icloud.com> wrote:

Just realized that even inout functions don’t let you do
member(object) = value.

The other difference is that an inout function can't be used to get a
member from an immutable value, whereas a keypath/subscript/property
access can.

--
-Dave
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I hope for Swift meta-programming features evolving alongside reflection (both topics imho still need a complete concept):
How about
collection.map(MyObject.myProperty.get)
or, to avoid possible ambiguity, something like
collection.map(.myProperty#get)
?

I also thought about adding a computed property or method to KeyPath to
get the callable behaviour, but it is really hard to distinguish between
the keypath and it's method, especially in the literal syntax. Adding a
separator like # would help but would bring the same new complexity like
a prefix operator. If we add something like this it should be there for
a broader use in my opinion.

After thinking a bit more about the topic I tend more to add callable
behaviour to instances via a keyword. Background is that I found
similarities to `init`.

`init` is a keyword in Swift which is called on the type name with given
parameters and always return `Self` (`init?` returns `Self?`). So I
think it would be consistent to add another keyword which enables the
same thing on an instance basis. Like `init` it could also be overloaded
and in this case even return different types.

My first proposal is `call` but this could be a bit too generic and
could also break existing code, so if you have better name suggestions
go ahead! :)

An example could look like this:

class ArrayWrapper<Element> {
    
    private var elements: [Element]
    
    // `init` as keyword is called on static level -> KeyPath(root: )
    init(elements: Element...) { self.elements = elements }
    
    // `call` as new keyword would make the object callable
    call(index: Int) -> Element { return self.elements[index] }
}

let a = ArrayWrapper(elements: 1, 2, 3)
a(index: 1) // a can now also act as a function 2

In this case, `a` could also be used as a normal function taking an
`Int` as parameter and returning an `Int` (because `Element` is replaced
by `Int`) after calling.

func someFunction(f: (Int) -> Int) -> Int {
  return f(1)
}
someFunction(f: a) // 2

In the same manner this could be implemented for KeyPath<Root, Value> to
act as (Root) -> Value. It could also act as a setter lens by having one
parameter declared as `inout`.

What do you think about this?

···

______________________

Benjamin Herzog

On Mon, Jul 17, 2017, at 02:03 PM, Tino Heth via swift-evolution wrote:

I hope for Swift meta-programming features evolving alongside reflection
(both topics imho still need a complete concept):
How about
collection.map(MyObject.myProperty.get)
or, to avoid possible ambiguity, something like
collection.map(.myProperty#get)
?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Guys, I see that this has now been implemented in Swift 5.2, but I hope that one small thing can be added.

Like it's great that I can now do something like:

let activeProjects = projects.filter(\.active)

It's beautiful and concise. But there's nothing built in for the opposite of that scenario, where you want to reject projects based on a boolean. Which just seems like something you can't leave out if you're already offering this feature. So could you please add either or both of the two following options?

let activeProjects = projects.filter(!\.canceled)
// or
let activeProjects = projects.except(\.canceled)
// or
let activeProjects = projects.without(\.canceled)
// or
let activeProjects = projects.reject(\.canceled)
// whatever you want to call it, I don't care that much

These are trivial to implement and I already have done so in my own projects. I hope you can still implement this into the language before 5.2 is officially released. :pray:

5.2 releases tomorrow, so probably not. :joy:

Oh scheiße, I didn't know.