You can write some fun functions around this and reuse existing machinery.
func get<Root, Value>(_ kp: KeyPath<Root, Value>) -> (Root) -> Value {
return { root in
root[keyPath: kp]
}
}
func combining<Root, Value>(
_ f: @escaping (Root) -> Value,
by g: @escaping (Value, Value) -> Value
)
-> (Value, Root)
-> Value {
return { value, root in
g(value, f(root))
}
}
And with those defined, your example becomes:
people.reduce(0, combining(get(\.vitae.age), by: +))
As a sidebar, the (A, A) -> Bool
shape also shows up in a bunch of generic algorithms (sorted
, min
, max
), so the their
function can be pretty handy:
func their<Root, Value>(
_ f: @escaping (Root) -> Value,
_ g: @escaping (Value, Value) -> Bool
)
-> (Root, Root) -> Bool {
return { g(f($0), f($1)) }
}
people.sorted(by: their(get(\.name), <))
And with the obvious Comparable
overload:
func their<Root, Value: Comparable>(
_ f: @escaping (Root) -> Value
)
-> (Root, Root) -> Bool {
return their(f, <)
}
people.sorted(by: their(get(\.name)))
I'm also a fan of defining the prefix ^
operator to simplify the noise of get
while we wait for more seamless key-path integration
prefix operator ^
prefix func ^ <Root, Value>(_ kp: KeyPath<Root, Value>) -> (Root) -> Value {
return get(kp)
}
Which makes the earlier examples read quite lovely!
people.reduce(0, combining(^\.vitae.age, by: +))
people.sorted(by: their(^\.name))