Last active
February 21, 2019 00:25
-
-
Save seanho/8b0dfd006cbc99f04afc705c5a442678 to your computer and use it in GitHub Desktop.
KeyPath based Lens implementation
This file contains hidden or 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
protocol LensType { | |
associatedtype Container | |
associatedtype View | |
func get() -> View | |
func set(_ newValue: View) -> Container | |
} | |
protocol KeyPathInstantiatable { | |
init(mapper: KeyPathMapper<Self>) | |
} | |
struct KeyPathMapper<C> { | |
let container: C | |
let keyPathToOverride: PartialKeyPath<C> | |
let valueToOverride: Any | |
init<U>(container: C, keyPathToOverride: KeyPath<C, U>, valueToOverride: U) { | |
self.container = container | |
self.keyPathToOverride = keyPathToOverride | |
self.valueToOverride = valueToOverride | |
} | |
func map<V>(_ keyPath: KeyPath<C, V>) -> V { | |
return keyPath == keyPathToOverride ? valueToOverride as! V : container[keyPath: keyPath] | |
} | |
} | |
struct KeyPathLens<C: KeyPathInstantiatable, V>: LensType { | |
let container: C | |
let keyPath: KeyPath<C, V> | |
init(container: C, keyPath: KeyPath<C, V>) { | |
self.container = container | |
self.keyPath = keyPath | |
} | |
func get() -> V { | |
return container[keyPath: keyPath] | |
} | |
func set(_ newValue: V) -> C { | |
let mapper = KeyPathMapper(container: container, keyPathToOverride: keyPath, valueToOverride: newValue) | |
return C.init(mapper: mapper) | |
} | |
} | |
protocol KeyPathLensIntrospectible: KeyPathInstantiatable { | |
func lens<V>(_ keyPath: KeyPath<Self, V>) -> KeyPathLens<Self, V> | |
func lens<V>(set keyPath: KeyPath<Self, V>, to newValue: V) -> Self | |
} | |
extension KeyPathLensIntrospectible { | |
func lens<V>(_ keyPath: KeyPath<Self, V>) -> KeyPathLens<Self, V> { | |
return KeyPathLens(container: self, keyPath: keyPath) | |
} | |
func lens<V>(set keyPath: KeyPath<Self, V>, to newValue: V) -> Self { | |
return lens(keyPath).set(newValue) | |
} | |
} |
This file contains hidden or 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 Person { | |
let firstName: String | |
let lastName: String | |
let tags: [String] | |
let age: Int? | |
init(firstName: String, lastName: String, tags: [String], age: Int?) { | |
self.firstName = firstName | |
self.lastName = lastName | |
self.tags = tags | |
self.age = age | |
} | |
} | |
extension Person: KeyPathLensIntrospectible { | |
init(mapper: KeyPathMapper<Person>) { | |
self.firstName = mapper.map(\.firstName) | |
self.lastName = mapper.map(\.lastName) | |
self.tags = mapper.map(\.tags) | |
self.age = mapper.map(\.age) | |
} | |
} |
This file contains hidden or 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
let person = Person(firstName: "Robert", lastName: "Lee", tags: ["Active"], age: 10) | |
let person2 = person | |
.lens(set: \.firstName, to: "Bob") | |
.lens(set: \.lastName, to: "L") | |
.lens(set: \.age, to: nil) | |
.lens(set: \.tags, to: []) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice. So with this in place, to add lensing to an existing type
X
, is just adding the mapper constructor, yeah? eg.