[swift-evolution] Pitch: Support for map and flatMap with smart key paths

Karl Wagner razielim at gmail.com
Fri Jun 9 19:16:23 CDT 2017


> On 7. Jun 2017, at 19:35, Adam Sharp via swift-evolution <swift-evolution at 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 at 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

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170610/6ef8d25c/attachment.html>


More information about the swift-evolution mailing list