Skip to content

Instantly share code, notes, and snippets.

@seanho
Last active February 21, 2019 00:25
Show Gist options
  • Save seanho/8b0dfd006cbc99f04afc705c5a442678 to your computer and use it in GitHub Desktop.
Save seanho/8b0dfd006cbc99f04afc705c5a442678 to your computer and use it in GitHub Desktop.
KeyPath based Lens implementation
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)
}
}
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)
}
}
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: [])
@sgleadow
Copy link

Nice. So with this in place, to add lensing to an existing type X, is just adding the mapper constructor, yeah? eg.

extension X: KeyPathLensIntrospectible {
    init(mapper: KeyPathMapper<X>) {
        // map properties based on key path
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment