Pitch: Reducing a sequence onto its own elements to simplify code and reduce errors

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 :slight_smile:

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))
5 Likes