-
-
Save haikusw/934e52e5d009207d43d37b5bd9039658 to your computer and use it in GitHub Desktop.
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 KeyPathUpdatable {} | |
extension KeyPathUpdatable { | |
func updating<LeafType>(_ keyPath: WritableKeyPath<Self, LeafType>, to value: LeafType) -> Self { | |
var copy = self | |
copy[keyPath: keyPath] = value | |
return copy | |
} | |
} | |
struct Book: KeyPathUpdatable { | |
var title: String | |
var author: String | |
var isbn: Int | |
var publishedYear: Int | |
} | |
let book = Book(title: "Something", author: "Noah", isbn: 1, publishedYear: 2020) | |
let updated = book.updating(\.title, to: "Else") | |
print(book) | |
print(updated) |
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
// playground with some alternatives and showing why the above is preferable | |
// and also showing and addition from the swift forum that is cool. | |
protocol KeyPathUpdatable {} | |
extension KeyPathUpdatable { | |
func updating<LeafType>(_ keyPath: WritableKeyPath<Self, LeafType>, to value: LeafType) -> Self { | |
var copy = self | |
copy[keyPath: keyPath] = value | |
return copy | |
} | |
// "one more thing" | |
// from swift forum on the topic: | |
// https://forums.swift.org/t/nice-way-of-copying-an-immutable-value-while-changing-only-a-few-of-its-many-properties | |
static func .= <V>(lhs: Self, rhs: (WritableKeyPath<Self, V>, V)) -> Self { | |
return lhs.updating(rhs.0, to: rhs.1) | |
} | |
} | |
infix operator .=: AdditionPrecedence | |
struct Book: KeyPathUpdatable { | |
var title: String | |
var author: String | |
var isbn: Int | |
var publishedYear: Int | |
} | |
// Without this you have to use some less pretty ways. | |
// One an alternative approach that is not a protocol and only works for this struct: | |
// "LastWay" flavors require a custom init constructor that takes an instance and optional parameters | |
// so you can use an optional parameter value if present or use the value in the passed in instance. | |
extension Book { | |
init(_ book: Book, title: String? = nil, author: String? = nil, isbn: Int? = nil, publishedYear: Int? = nil) { | |
self.init(title: title ?? book.title, | |
author: author ?? book.author, | |
isbn: isbn ?? book.isbn, | |
publishedYear: publishedYear ?? book.publishedYear) | |
} | |
func updating2(title: String? = nil, author: String? = nil, isbn: Int? = nil, publishedYear: Int? = nil) -> Book { | |
Book(self, title: title, author: author, isbn: isbn, publishedYear: publishedYear) | |
} | |
} | |
let book = Book(title: "Something", author: "Noah", isbn: 1, publishedYear: 2020) | |
let updated = book.updating(\.title, to: "Else") | |
// How you'd do this without theKeyPathUpdateble: | |
let updatedOldWay = Book(title: "Else", author: book.author, isbn: book.isbn, publishedYear: book.publishedYear) | |
let updatedAlternateWay = { () -> Book in var temp = book; temp.title = "Else"; return temp }() | |
// the "LastWay" extension makes it possible for this particular struct where you added this code, | |
// but not generically like keypath: | |
let updatedLastWay = Book(book, title: "Else") | |
let updatedLastLastWay = book.updating2(title: "Else") | |
let updatedOneMoreThing = book .= (\.title, "Else!") | |
.= (\.publishedYear, 2021) | |
print(book) | |
print(updated) | |
print(updatedOldWay) | |
print(updatedAlternateWay) | |
print(updatedLastWay) | |
print(updatedLastLastWay) | |
print(updatedOneMoreThing) | |
/* | |
Book(title: "Something", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else", author: "Noah", isbn: 1, publishedYear: 2020) | |
Book(title: "Else!", author: "Noah", isbn: 1, publishedYear: 2021) | |
*/ |
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
// nice way to update one field in a let struct: | |
// | |
// This version provides two approaches. | |
// One with explicit keypaths and one using direct dot notation: | |
// for the direct . notation version | |
@dynamicMemberLookup | |
struct Updater<Root> { | |
let root: Root | |
subscript<Value>(dynamicMember keyPath: WritableKeyPath<Root, Value>) -> (Value) -> Root { | |
{ [root] value in | |
var copy = root | |
copy[keyPath: keyPath] = value | |
return copy | |
} | |
} | |
} | |
protocol KeyPathUpdatable { | |
var update: Updater<Self> { get } | |
} | |
extension KeyPathUpdatable { | |
var update: Updater<Self> { .init(root: self) } | |
} | |
extension KeyPathUpdatable { | |
func updating<LeafType>(_ keyPath: WritableKeyPath<Self, LeafType>, to value: LeafType) -> Self { | |
var copy = self | |
copy[keyPath: keyPath] = value | |
return copy | |
} | |
} | |
//// adds operator, but I don't personally like adding operators so I'm leaving this commented out. | |
//extension KeyPathUpdatable { | |
// // "one more thing" | |
// // from swift forum on the topic: | |
// // https://forums.swift.org/t/nice-way-of-copying-an-immutable-value-while-changing-only-a-few-of-its-many-properties | |
// static func .= <V>(lhs: Self, rhs: (WritableKeyPath<Self, V>, V)) -> Self { | |
// return lhs.updating(rhs.0, to: rhs.1) | |
// } | |
//} | |
//infix operator .=: AdditionPrecedence | |
struct Book: KeyPathUpdatable { | |
var title: String | |
var author: String | |
var isbn: Int | |
var publishedYear: Int | |
} | |
// example uses showing different syntax | |
let book = Book(title: "Something", author: "Noah", isbn: 1, publishedYear: 2020) | |
// make a copy with an modified title | |
let updated = book.updating(\.title, to: "Else") | |
print(book) | |
print(updated) | |
// the operator example | |
//let updatedOneMoreThing = book .= (\.title, "Else!") | |
// .= (\.publishedYear, 2021) | |
// print(updatedOneMoreThing) | |
// try out the alternative using dynamic lookup instead of keypath | |
let bbook = book.update.title( "Alternative Book - radical!") | |
print(bbook) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
testKeyPathUpdateableVariants2.swift is probably the form I'll end up putting together as a utility.