Pitch: Support for map and flatMap with smart key paths

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

5 Likes

:100:

···

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

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

:100:

···

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

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

This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

···

On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org> wrote:

:100:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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

+1, it seems like a wonderful addition that would make a lot of code much
shorter.

···

On Wed, Jun 7, 2017 at 7:35 PM, Adam Sharp via swift-evolution < swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great
addition to Swift.

It seems like it might be straightforward to add overloads of `map` and
`flatMap` to the standard library to make use of the new functionality:

        let managers = flatOrganisation.managers
        let allEmployees = Set(managers.flatMap(\.directReports))
        let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a
functional style. It makes a lot of sense for collections, and possibly for
Optional too (although as far as I can see optional chaining is more or
less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered
for the Swift 4 release. I’d be happy to have a go at writing a proposal if
there’s interest!

–Adam

--
Víctor Pimentel

I had the need for the same thing today and made it as an extension on Sequence.

extension Sequence {
    func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
        return self.map {
            $0[keyPath: keyPath]
        }
    }
    
    func flatMap<T>(_ keyPath: KeyPath<Element, T?>) -> [T] {
        return self.flatMap {
            $0[keyPath: keyPath]
        }
    }
}

···

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

let managers = flatOrganisation.managers
let allEmployees = Set(managers.flatMap(\.directReports))
let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

Sorry. I might be missing something. Why is this better than:

let allEmployees = Set(managers.flatMap { $0.directReports }

?

···

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

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

Working demo:

struct VirtualKeyPath<Root, Value> {
    let block: (Root) -> Value
    func evaluate(on: Root) -> Value { return block(on) }
}

// If we could extend 'Any', this would be possible...
//extension Any {
// subscript(keyPath: VirtualKeyPath<Self, Value>) -> Value {
// return keyPath.evaluate(on: self)
// }
//}

extension KeyPath where Value: Collection {
    
    func map<T>(_ descendent: KeyPath<Value.Element, T>) -> VirtualKeyPath<Root, [T]> {
        return VirtualKeyPath<Root, [T]> { (obj: Root) -> [T] in
            return obj[keyPath: self].map { $0[keyPath: descendent] }
        }
    }
}

extension VirtualKeyPath where Value: Collection {
    
    func map<T>(_ descendent: KeyPath<Value.Element, T>) -> VirtualKeyPath<Root, [T]> {
        return VirtualKeyPath<Root, [T]> { (obj: Root) -> [T] in
            return self.evaluate(on: obj).map { $0[keyPath: descendent] }
        }
    }
}

struct Person {
    let name: String
}
struct Department {
    let people: [Person]
}

let nameLengths = (\Department.people).map(\.name).map(\.characters.count)

let testObj = Department(people: [Person(name: "Alice"),
                                  Person(name: "Bob"),
                                  Person(name: "Claire"),
                                  Person(name: "David")])

kp.evaluate(on: testObj) // returns [5, 3, 6, 5]
As far as making this kind of thing easier in the language is concerned, one thing I can think of is allowing another \ to end the key-path expression, rather than enclosing it with brackets. So:

let nameLengths = (\Department.people).map(\.name).map(\.characters.count)

Becomes:

let nameLengths = \Department.people\.map(\.name).map(\.characters.count)

And that’s it, I think. It’s quite nice as-is.

- Karl

···

On 7. Jun 2017, at 19:35, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

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

+1. Would think that all variants should exist on Optional too unless it
would be harmful.

···

On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution < swift-evolution@swift.org> wrote:

This is a great idea, and ought to be easy enough to bring forward! +1
from me!

-Michael

> On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution < > swift-evolution@swift.org> wrote:
>
> :100:
>
>> On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> The new smart key path feature is really lovely, and feels like a great
addition to Swift.
>>
>> It seems like it might be straightforward to add overloads of `map` and
`flatMap` to the standard library to make use of the new functionality:
>>
>> let managers = flatOrganisation.managers
>> let allEmployees = Set(managers.flatMap(\.directReports))
>> let employeeNames = Set(allEmployees.map(\.name))
>>
>> This feels like a really natural way of working with key paths in a
functional style. It makes a lot of sense for collections, and possibly for
Optional too (although as far as I can see optional chaining is more or
less equivalent, and with more compact syntax).
>>
>> I’m hoping that this might be low-hanging fruit that could be
considered for the Swift 4 release. I’d be happy to have a go at writing a
proposal if there’s interest!
>>
>> –Adam
>>
>> _______________________________________________
>> 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

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

It reads better and feels more natural. In my mind, it’s similar to the difference between

["1", "2", "3"].flatMap { Double($0) }

and

["1", "2", "3"].flatMap(Double.init)

Saagar Jha

···

On Jun 9, 2017, at 16:06, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Sorry. I might be missing something. Why is this better than:

let allEmployees = Set(managers.flatMap { $0.directReports }

?

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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

Sorry. I might be missing something. Why is this better than:

let allEmployees = Set(managers.flatMap { $0.directReports }

?

For the same reasons

managers.flatMap(calculateReports(_:))

Is better than:

managers.flatMap { calculateReports($0) }

Quite a few people enjoy this tearse, functional style.

···

On 9 Jun 2017, at 19:06, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

   let managers = flatOrganisation.managers
   let allEmployees = Set(managers.flatMap(\.directReports))
   let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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

+1, I really like this. It would also align nicely with the method type
flattening in SE-0042
<https://github.com/apple/swift-evolution/blob/master/proposals/0042-flatten-method-types.md&gt;
(once it gets implemented), because passing keypaths (i.e., unbound
property references) and unbound parameterless method references to
map/flatMap would look nearly the same:

struct Person {
  let firstName: String
  let lastName: String
  func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this
will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound
methods references, which was discussed during the keypath reviews. (Even
with that, I believe it would be more work though to get rid of the
explicit type name in the function case.)

···

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

+1. Would think that all variants should exist on Optional too unless it
would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution < > swift-evolution@swift.org> wrote:

This is a great idea, and ought to be easy enough to bring forward! +1
from me!

-Michael

> On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution < >> swift-evolution@swift.org> wrote:
>
> :100:
>
>> On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution < >> swift-evolution@swift.org> wrote:
>>
>> The new smart key path feature is really lovely, and feels like a
great addition to Swift.
>>
>> It seems like it might be straightforward to add overloads of `map`
and `flatMap` to the standard library to make use of the new functionality:
>>
>> let managers = flatOrganisation.managers
>> let allEmployees = Set(managers.flatMap(\.directReports))
>> let employeeNames = Set(allEmployees.map(\.name))
>>
>> This feels like a really natural way of working with key paths in a
functional style. It makes a lot of sense for collections, and possibly for
Optional too (although as far as I can see optional chaining is more or
less equivalent, and with more compact syntax).
>>
>> I’m hoping that this might be low-hanging fruit that could be
considered for the Swift 4 release. I’d be happy to have a go at writing a
proposal if there’s interest!
>>
>> –Adam
>>
>> _______________________________________________
>> 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

_______________________________________________
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

It reads better and feels more natural. In my mind, it’s similar to the difference between

["1", "2", "3"].flatMap { Double($0) }

and

["1", "2", "3"].flatMap(Double.init)

Saagar Jha

I am not convinced this syntactic sugar is worth complicating the
language or library for, but if it is, IMO the right thing is to make a
keypath be-a subtype of the appropriate function type, rather than to
start down the path of creating a keypath overload for every method that
takes a closure argument.

···

on Fri Jun 09 2017, Saagar Jha <swift-evolution@swift.org> wrote:

On Jun 9, 2017, at 16:06, Max Moiseev via swift-evolution <swift-evolution@swift.org> wrote:

Sorry. I might be missing something. Why is this better than:

let allEmployees = Set(managers.flatMap { $0.directReports }

?

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution >>> <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map`
and `flatMap` to the standard library to make use of the new
functionality:

  let managers = flatOrganisation.managers
  let allEmployees = Set(managers.flatMap(\.directReports))
  let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a
functional style. It makes a lot of sense for collections, and
possibly for Optional too (although as far as I can see optional
chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be
considered for the Swift 4 release. I’d be happy to have a go at
writing a proposal if there’s interest!

–Adam

_______________________________________________
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

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

--
-Dave

1 Like

It should be possible to achieve Ruby-like generality in Swift with a protocol for “thing that can converted to a transform function.” That wouldn’t need a special & operator.

Here’s a sketch. This sketch doesn’t compile — maybe not enough of Swift 4 is there yet for it to work, or maybe I am missing something obvious and need to go to sleep now — but it’s close enough to suggest the approach:

    public protocol TransformConvertible { // or whatever you want to call it
      associatedtype From
      associatedtype To
      
      var transform: (From) -> To { get }
    }

    extension KeyPath: TransformConvertible {
      public typealias From = Root
      public typealias To = Value
      
      public var transform: (Root) -> Value {
        return { $0[keypath: self] }
      }
    }

    extension Sequence {
      public func map<T, U>(_ transformSource: U) -> [T]
           where U: TransformConvertible,
                 U.From == Element,
                 U.To == T {
        return map(transformSource.transform)
      }
    }

This seems a bit more ambitious, perhaps not suitable for this round of Swift evolution work. But I throw it out there at least to show that supporting people.map(\.firstName) today would not preclude a generic keypath → function mechanism in the future:

A flavor of map that accepts a keypath today could be generalized to accept TransformConvertible in the future without breaking existing code.
When calling a function that doesn’t know how to work with TransformConvertible, you could use (Foo.bar).transform, no special operator needed.

Cheers,

Paul

P.S. Largely irrelevant Ruby aside: Ruby’s & is not a free-floating operator, but part of the method invocation syntax indicating that the following arg should be treated as a block. Ruby calls a to_proc method on whatever is in that position. Symbol implements to_proc by returning a lambda that calls the method named by the symbol on the lambda’s first arg. Very much the duck-typed version of TransformConvertible above.

···

On Jun 7, 2017, at 10:21 PM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

-1

A -1 from me may be surprising. I'm excited about key path composition and generic solutions, e.g. this experiment with lenses: https://twitter.com/stephencelis/status/863916921577758721

But I'd prefer a reusable solution for converting key paths into functions.

Heaven help me for this Rubyism, but a prefix "&" operator (or, maybe better yet, some implicit mechanism) could convert a key-path to a function that passes a root value to a key path...

  people.map(&\.firstName)

This way any function that takes a transformation from "whole" to "part" could take a key path. Requiring an overload per instance is less flexible.

Stephen

On Jun 7, 2017, at 10:58 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
 let firstName: String
 let lastName: String
 func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org> wrote:

:100:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

    let managers = flatOrganisation.managers
    let allEmployees = Set(managers.flatMap(\.directReports))
    let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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

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

-1

A -1 from me may be surprising. I'm excited about key path composition and generic solutions, e.g. this experiment with lenses: https://twitter.com/stephencelis/status/863916921577758721

But I'd prefer a reusable solution for converting key paths into functions.

Heaven help me for this Rubyism, but a prefix "&" operator (or, maybe better yet, some implicit mechanism) could convert a key-path to a function that passes a root value to a key path...

   people.map(&\.firstName)

This way any function that takes a transformation from "whole" to "part" could take a key path. Requiring an overload per instance is less flexible.

Stephen

···

On Jun 7, 2017, at 10:58 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
  let firstName: String
  let lastName: String
  func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

> On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org> wrote:
>
> :100:
>
>> On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:
>>
>> The new smart key path feature is really lovely, and feels like a great addition to Swift.
>>
>> It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:
>>
>> let managers = flatOrganisation.managers
>> let allEmployees = Set(managers.flatMap(\.directReports))
>> let employeeNames = Set(allEmployees.map(\.name))
>>
>> This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).
>>
>> I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!
>>
>> –Adam
>>
>> _______________________________________________
>> 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

_______________________________________________
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
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

1 Like

this work,

prefix operator *

prefix func *<Root, Value>(keyPath: KeyPath<Root, Value>) -> (Root) -> Value
{

    return { $0[keyPath: keyPath] }

}

["Hello, World"].map(*\String.count) // [12]

···

2017-06-08 12:19 GMT+08:00 Paul Cantrell via swift-evolution < swift-evolution@swift.org>:

It should be possible to achieve Ruby-like generality in Swift with a
protocol for “thing that can converted to a transform function.” That
wouldn’t need a special & operator.

Here’s a sketch. This sketch doesn’t compile — maybe not enough of Swift 4
is there yet for it to work, or maybe I am missing something obvious and
need to go to sleep now — but it’s close enough to suggest the approach:

    public protocol TransformConvertible { // or whatever you want to
call it
      associatedtype From
      associatedtype To

      var transform: (From) -> To { get }
    }

    extension KeyPath: TransformConvertible {
      public typealias From = Root
      public typealias To = Value

      public var transform: (Root) -> Value {
        return { $0[keypath: self] }
      }
    }

    extension Sequence {
      public func map<T, U>(_ transformSource: U) -> [T]
           where U: TransformConvertible,
                 U.From == Element,
                 U.To == T {
        return map(transformSource.transform)
      }
    }

This seems a bit more ambitious, perhaps not suitable for this round of
Swift evolution work. But I throw it out there at least to show that
supporting people.map(\.firstName) today *would not preclude* a generic
keypath → function mechanism in the future:

   - A flavor of map that accepts a keypath today could be generalized to
   accept TransformConvertible in the future without breaking existing code.
   - When calling a function that doesn’t know how to work
   with TransformConvertible, you could use (Foo.bar).transform, no special
   operator needed.

Cheers,

Paul

P.S. Largely irrelevant Ruby aside: Ruby’s & is not a free-floating
operator, but part of the method invocation syntax indicating that the
following arg should be treated as a block. Ruby calls a to_proc method on
whatever is in that position. Symbol implements to_proc by returning a
lambda that calls the method named by the symbol on the lambda’s first arg.
Very much the duck-typed version of TransformConvertible above.

On Jun 7, 2017, at 10:21 PM, Stephen Celis via swift-evolution < > swift-evolution@swift.org> wrote:

-1

A -1 from me may be surprising. I'm excited about key path composition and
generic solutions, e.g. this experiment with lenses: https://twitter.com/
stephencelis/status/863916921577758721

But I'd prefer a reusable solution for converting key paths into functions.

Heaven help me for this Rubyism, but a prefix "&" operator (or, maybe
better yet, some implicit mechanism) could convert a key-path to a function
that passes a root value to a key path...

  people.map(&\.firstName)

This way any function that takes a transformation from "whole" to "part"
could take a key path. Requiring an overload per instance is less flexible.

Stephen

On Jun 7, 2017, at 10:58 PM, Tony Allevato via swift-evolution < > swift-evolution@swift.org> wrote:

+1, I really like this. It would also align nicely with the method type
flattening in SE-0042 (once it gets implemented), because passing keypaths
(i.e., unbound property references) and unbound parameterless method
references to map/flatMap would look nearly the same:

struct Person {
 let firstName: String
 let lastName: String
 func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042,
this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote
unbound methods references, which was discussed during the keypath reviews.
(Even with that, I believe it would be more work though to get rid of the
explicit type name in the function case.)

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
+1. Would think that all variants should exist on Optional too unless it
would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution < > swift-evolution@swift.org> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1
from me!

-Michael

On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution < > swift-evolution@swift.org> wrote:

:100:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution < > swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great
addition to Swift.

It seems like it might be straightforward to add overloads of `map` and
`flatMap` to the standard library to make use of the new functionality:

    let managers = flatOrganisation.managers
    let allEmployees = Set(managers.flatMap(\.directReports))
    let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a
functional style. It makes a lot of sense for collections, and possibly for
Optional too (although as far as I can see optional chaining is more or
less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered
for the Swift 4 release. I’d be happy to have a go at writing a proposal if
there’s interest!

–Adam

_______________________________________________
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

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

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

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 <https://github.com/apple/swift-evolution/blob/master/proposals/0042-flatten-method-types.md&gt; (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
  let firstName: String
  let lastName: String
  func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

When it comes to unbound method references, personally, I would love to see us ditch currying in some future version of Swift and move to full-blown partial application instead. We would need variadic generics if we wanted to expose them as nicely-typed objects as we do with KeyPaths.

Anyway, I think what you want is something like this (where VirtualKeyPath is a custom subclass of KeyPath which is lazily-evaluated using a closure).

extension KeyPath where Value: Collection {
    func map<T>(_ descendent: KeyPath<Value, T>) -> VirtualKeyPath<Root, [T]> {
        return VirtualKeyPath<Root, [T]> {
            (obj: Root) -> [T] in obj[keypath: self].map { $0[keypath: descendent] }
        }
    }
}

\Department.people.map(\.fullName).characters.count // type: VirtualKeyPath<Department, [Int]>

Custom subclasses of KeyPath are not allowed, so you can’t actually do this. I don’t know, maybe it wouldn’t be much overhead to add the one, closure-based VirtualKeyPath — clearly the architecture is meant to be flexible. Maybe it’s better to wait until Swift 5 for that, though.

- Karl

···

On 8. Jun 2017, at 04:58, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

> On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> :100:
>
>> On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> The new smart key path feature is really lovely, and feels like a great addition to Swift.
>>
>> It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:
>>
>> let managers = flatOrganisation.managers
>> let allEmployees = Set(managers.flatMap(\.directReports))
>> let employeeNames = Set(allEmployees.map(\.name))
>>
>> This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).
>>
>> I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!
>>
>> –Adam
>>
>> _______________________________________________
>> 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 <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

Ah, quite ingenious, Susan!

And you made me realize that the only reason my no-operator-needed code didn’t compile is that I had keypath instead of keyPath. So it was indeed “Paul needs to go to sleep now” option, which I will now do. :roll_eyes:

P

···

On Jun 7, 2017, at 11:34 PM, Susan Cheng <susan.doggie@gmail.com> wrote:

this work,

prefix operator *

prefix func *<Root, Value>(keyPath: KeyPath<Root, Value>) -> (Root) -> Value {
    return { $0[keyPath: keyPath] }
}

["Hello, World"].map(*\String.count) // [12]

2017-06-08 12:19 GMT+08:00 Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:
It should be possible to achieve Ruby-like generality in Swift with a protocol for “thing that can converted to a transform function.” That wouldn’t need a special & operator.

Here’s a sketch. This sketch doesn’t compile — maybe not enough of Swift 4 is there yet for it to work, or maybe I am missing something obvious and need to go to sleep now — but it’s close enough to suggest the approach:

    public protocol TransformConvertible { // or whatever you want to call it
      associatedtype From
      associatedtype To
      
      var transform: (From) -> To { get }
    }

    extension KeyPath: TransformConvertible {
      public typealias From = Root
      public typealias To = Value
      
      public var transform: (Root) -> Value {
        return { $0[keypath: self] }
      }
    }

    extension Sequence {
      public func map<T, U>(_ transformSource: U) -> [T]
           where U: TransformConvertible,
                 U.From == Element,
                 U.To == T {
        return map(transformSource.transform)
      }
    }

This seems a bit more ambitious, perhaps not suitable for this round of Swift evolution work. But I throw it out there at least to show that supporting people.map(\.firstName) today would not preclude a generic keypath → function mechanism in the future:

A flavor of map that accepts a keypath today could be generalized to accept TransformConvertible in the future without breaking existing code.
When calling a function that doesn’t know how to work with TransformConvertible, you could use (Foo.bar).transform, no special operator needed.

Cheers,

Paul

P.S. Largely irrelevant Ruby aside: Ruby’s & is not a free-floating operator, but part of the method invocation syntax indicating that the following arg should be treated as a block. Ruby calls a to_proc method on whatever is in that position. Symbol implements to_proc by returning a lambda that calls the method named by the symbol on the lambda’s first arg. Very much the duck-typed version of TransformConvertible above.

On Jun 7, 2017, at 10:21 PM, Stephen Celis via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

-1

A -1 from me may be surprising. I'm excited about key path composition and generic solutions, e.g. this experiment with lenses: https://twitter.com/stephencelis/status/863916921577758721

But I'd prefer a reusable solution for converting key paths into functions.

Heaven help me for this Rubyism, but a prefix "&" operator (or, maybe better yet, some implicit mechanism) could convert a key-path to a function that passes a root value to a key path...

  people.map(&\.firstName)

This way any function that takes a transformation from "whole" to "part" could take a key path. Requiring an overload per instance is less flexible.

Stephen

On Jun 7, 2017, at 10:58 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
 let firstName: String
 let lastName: String
 func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

:100:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

    let managers = flatOrganisation.managers
    let allEmployees = Set(managers.flatMap(\.directReports))
    let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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 <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 <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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

:D

Great example for what I was trying to say. We can compose such a thing with existing code and not require an overload per.

···

On Jun 8, 2017, at 12:34 AM, Susan Cheng <susan.doggie@gmail.com> wrote:

this work,

prefix operator *

prefix func *<Root, Value>(keyPath: KeyPath<Root, Value>) -> (Root) -> Value {
    return { $0[keyPath: keyPath] }
}

["Hello, World"].map(*\String.count) // [12]

2017-06-08 12:19 GMT+08:00 Paul Cantrell via swift-evolution <swift-evolution@swift.org>:
It should be possible to achieve Ruby-like generality in Swift with a protocol for “thing that can converted to a transform function.” That wouldn’t need a special & operator.

Here’s a sketch. This sketch doesn’t compile — maybe not enough of Swift 4 is there yet for it to work, or maybe I am missing something obvious and need to go to sleep now — but it’s close enough to suggest the approach:

    public protocol TransformConvertible { // or whatever you want to call it
      associatedtype From
      associatedtype To
      
      var transform: (From) -> To { get }
    }

    extension KeyPath: TransformConvertible {
      public typealias From = Root
      public typealias To = Value
      
      public var transform: (Root) -> Value {
        return { $0[keypath: self] }
      }
    }

    extension Sequence {
      public func map<T, U>(_ transformSource: U) -> [T]
           where U: TransformConvertible,
                 U.From == Element,
                 U.To == T {
        return map(transformSource.transform)
      }
    }

This seems a bit more ambitious, perhaps not suitable for this round of Swift evolution work. But I throw it out there at least to show that supporting people.map(\.firstName) today would not preclude a generic keypath → function mechanism in the future:

  • A flavor of map that accepts a keypath today could be generalized to accept TransformConvertible in the future without breaking existing code.
  • When calling a function that doesn’t know how to work with TransformConvertible, you could use (Foo.bar).transform, no special operator needed.

Cheers,

Paul

P.S. Largely irrelevant Ruby aside: Ruby’s & is not a free-floating operator, but part of the method invocation syntax indicating that the following arg should be treated as a block. Ruby calls a to_proc method on whatever is in that position. Symbol implements to_proc by returning a lambda that calls the method named by the symbol on the lambda’s first arg. Very much the duck-typed version of TransformConvertible above.

On Jun 7, 2017, at 10:21 PM, Stephen Celis via swift-evolution <swift-evolution@swift.org> wrote:

-1

A -1 from me may be surprising. I'm excited about key path composition and generic solutions, e.g. this experiment with lenses: https://twitter.com/stephencelis/status/863916921577758721

But I'd prefer a reusable solution for converting key paths into functions.

Heaven help me for this Rubyism, but a prefix "&" operator (or, maybe better yet, some implicit mechanism) could convert a key-path to a function that passes a root value to a key path...

  people.map(&\.firstName)

This way any function that takes a transformation from "whole" to "part" could take a key path. Requiring an overload per instance is less flexible.

Stephen

On Jun 7, 2017, at 10:58 PM, Tony Allevato via swift-evolution <swift-evolution@swift.org> wrote:

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
 let firstName: String
 let lastName: String
 func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org> wrote:

:100:

On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org> wrote:

The new smart key path feature is really lovely, and feels like a great addition to Swift.

It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:

    let managers = flatOrganisation.managers
    let allEmployees = Set(managers.flatMap(\.directReports))
    let employeeNames = Set(allEmployees.map(\.name))

This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).

I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!

–Adam

_______________________________________________
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

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

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

+1, I really like this. It would also align nicely with the method type flattening in SE-0042 <https://github.com/apple/swift-evolution/blob/master/proposals/0042-flatten-method-types.md&gt; (once it gets implemented), because passing keypaths (i.e., unbound property references) and unbound parameterless method references to map/flatMap would look nearly the same:

struct Person {
  let firstName: String
  let lastName: String
  func fullName() -> String { return "\(firstName) \(lastName)" }
}

let people: [Person]
let firstNames = people.map(\.firstName)
let fullNames = people.map(Person.fullName)  // because after SE-0042, this will be (Person) -> String, not (Person) -> () -> String

Especially if there's a move in the future to also use \. to denote unbound methods references, which was discussed during the keypath reviews. (Even with that, I believe it would be more work though to get rid of the explicit type name in the function case.)

When it comes to unbound method references, personally, I would love to see us ditch currying in some future version of Swift and move to full-blown partial application instead. We would need variadic generics if we wanted to expose them as nicely-typed objects as we do with KeyPaths.

Anyway, I think what you want is something like this (where VirtualKeyPath is a custom subclass of KeyPath which is lazily-evaluated using a closure).

extension KeyPath where Value: Collection {
    func map<T>(_ descendent: KeyPath<Value, T>) -> VirtualKeyPath<Root, [T]> {
        return VirtualKeyPath<Root, [T]> {
            (obj: Root) -> [T] in obj[keypath: self].map { $0[keypath: descendent] }
        }
    }
}

\Department.people.map(\.fullName).characters.count // type: VirtualKeyPath<Department, [Int]>

Custom subclasses of KeyPath are not allowed, so you can’t actually do this. I don’t know, maybe it wouldn’t be much overhead to add the one, closure-based VirtualKeyPath — clearly the architecture is meant to be flexible. Maybe it’s better to wait until Swift 5 for that, though.

- Karl

Two corrections to myself:

1) It’s KeyPath<Value.Element, T>

2) You could implement VirtualKeyPath today, but you wouldn’t get the chaining syntax. You’d have to just keep map-ping it every time:

\Department.people.map(\.fullName).map(\.characters.count) // type: VirtualKeyPath<Department, [Int]>

- Karl

···

On 10. Jun 2017, at 01:42, Karl Wagner <razielim@gmail.com> wrote:

On 8. Jun 2017, at 04:58, Tony Allevato via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Wed, Jun 7, 2017 at 6:11 PM Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
+1. Would think that all variants should exist on Optional too unless it would be harmful.
On Wed, Jun 7, 2017 at 20:13 Michael J LeHew Jr via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
This is a great idea, and ought to be easy enough to bring forward! +1 from me!

-Michael

> On Jun 7, 2017, at 11:18 AM, Matt Diephouse via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> :100:
>
>> On Jun 7, 2017, at 10:35 AM, Adam Sharp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>>
>> The new smart key path feature is really lovely, and feels like a great addition to Swift.
>>
>> It seems like it might be straightforward to add overloads of `map` and `flatMap` to the standard library to make use of the new functionality:
>>
>> let managers = flatOrganisation.managers
>> let allEmployees = Set(managers.flatMap(\.directReports))
>> let employeeNames = Set(allEmployees.map(\.name))
>>
>> This feels like a really natural way of working with key paths in a functional style. It makes a lot of sense for collections, and possibly for Optional too (although as far as I can see optional chaining is more or less equivalent, and with more compact syntax).
>>
>> I’m hoping that this might be low-hanging fruit that could be considered for the Swift 4 release. I’d be happy to have a go at writing a proposal if there’s interest!
>>
>> –Adam
>>
>> _______________________________________________
>> 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 <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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution