Skip to content

Instantly share code, notes, and snippets.

@karwa
Created June 10, 2017 02:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karwa/42f6a14f6d476e9b002184cef0da9959 to your computer and use it in GitHub Desktop.
Save karwa/42f6a14f6d476e9b002184cef0da9959 to your computer and use it in GitHub Desktop.
VirtualKeyPath
struct VirtualKeyPath<Root, Value> {
private let block: (Root) -> Value
init(block: @escaping (Root) -> Value) { self.block = block }
}
// Evaluation
extension VirtualKeyPath {
func evaluate(on: Root) -> Value { return block(on) }
}
// If only we could extend 'Any'...
//extension Any {
// subscript(keyPath: VirtualKeyPath<Self, Value>) -> Value {
// return keyPath.evaluate(on: self)
// }
//}
// Appending... we can't use the nice syntax :(
extension VirtualKeyPath {
func append<T>(_ next: KeyPath<Value, T>) -> VirtualKeyPath<Root, T> {
return VirtualKeyPath<Root, T> { (obj: Root) -> T in
return self.evaluate(on: obj)[keyPath: next]
}
}
func append<T>(_ next: VirtualKeyPath<Value, T>) -> VirtualKeyPath<Root, T> {
return VirtualKeyPath<Root, T> { (obj: Root) -> T in
return next.evaluate(on: self.evaluate(on: obj))
}
}
}
extension KeyPath {
func append<T>(_ next: VirtualKeyPath<Value, T>) -> VirtualKeyPath<Root, T> {
return VirtualKeyPath<Root, T> { (obj: Root) -> T in
return next.evaluate(on: obj[keyPath: self])
}
}
}
// Optional chaining.
extension VirtualKeyPath {
func append<Wrapped, T>(_ next: KeyPath<Wrapped, T>) -> VirtualKeyPath<Root, T?> where Value == Optional<Wrapped> {
return VirtualKeyPath<Root, T?> { (obj: Root) -> T? in
return self.evaluate(on: obj)?[keyPath: next]
}
}
func append<Wrapped, T>(_ next: VirtualKeyPath<Wrapped, T?>) -> VirtualKeyPath<Root, T?> where Value == Optional<Wrapped> {
return VirtualKeyPath<Root, T?> { (obj: Root) -> T? in
return self.evaluate(on: obj).flatMap { next.evaluate(on: $0) }
}
}
}
// Collection operations.
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] }
}
}
// subscript(i: Value.Index) -> VirtualKeyPath<Root, Value.Element> { return append(\.[i]) }
var first: VirtualKeyPath<Root, Value.Element?> { return append(\.first) }
var count: VirtualKeyPath<Root, Value.IndexDistance> { return append(\.count) }
}
// Test.
struct Person {
let name: String
}
struct Department {
let people: [Person]
}
let kp = (\Department.people).map(\.name).first.append(\.characters.count)
let testObj = Department(people: [Person(name: "Alice"),
Person(name: "Bob"),
Person(name: "Claire"),
Person(name: "David"),
Person(name: "Ezekial")])
let emptyObj = Department(people: [])
print(kp.evaluate(on: testObj))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment