Created
June 10, 2017 02:09
-
-
Save karwa/42f6a14f6d476e9b002184cef0da9959 to your computer and use it in GitHub Desktop.
VirtualKeyPath
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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