-
-
Save nicklockwood/9b4aac87e7f88c80e932ba3c843252df to your computer and use it in GitHub Desktop.
/// Withable is a simple protocol to make constructing | |
/// and modifying objects with multiple properties | |
/// more pleasant (functional, chainable, point-free) | |
public protocol Withable { | |
init() | |
} | |
public extension Withable { | |
/// Construct a new instance, setting an arbitrary subset of properties | |
init(with config: (inout Self) -> Void) { | |
self.init() | |
config(&self) | |
} | |
/// Create a copy, overriding an arbitrary subset of properties | |
func with(_ config: (inout Self) -> Void) -> Self { | |
var copy = self | |
config(©) | |
return copy | |
} | |
} | |
//--------------------------------------------------- | |
// Example struct | |
struct Foo: Withable { | |
var bar: Int = 0 | |
var baz: Bool = false | |
} | |
// Construct a foo, setting an arbitrary subset of properties | |
let foo = Foo { $0.bar = 5 } | |
// Make a copy of foo, overriding an arbitrary subset of properties | |
let foo2 = foo.with { $0.bar = 7; $0.baz = true } | |
// Test | |
print("\(foo.bar), \(foo2.bar)") // 5, 7 |
This is one of the things Lenses can do.
I find a nice addition to this to be
public extension Optional where Wrapped: Withable {
func with(_ config: (inout Wrapped) -> Void) -> Wrapped {
switch self {
case .none: return Wrapped.init(with: config)
case .some(let object): return object.with(config)
}
}
}
with this, I can modify or initialize an optional value in the same code path; good for an interface that updates the value potentially many times, but may start with it empty.
I'm using this little global bad boy:
/// Returns mutated copy of value
public func modify<A>(_ value: A, _ f: (inout A) throws -> Void) rethrows -> A {
var copy = value
try f(©)
return copy
}
Alongside with curried version:
public enum Fn {
/// Curried version of modify function
static func modify<A>(_ f: @escaping (inout A) -> Void) -> (A) -> A {
return { Fx.modify($0, f) }
}
}
So instead of [].map { modify($0) { ... } }
You can write [].map(Fn.modify { ... })
Basically it converts (inout A) -> Void
to (A) -> A
function
Nice Nick, but now you've got me wondering what interesting project you're using this in... :)
it seems like builder pattern?
@dcutting as-yet unannounced. I did try using it in SwiftFormat but it didn't play nice with Xcode 9.2 for some reason (I don't want to drop support for Sierra just yet).
@wsof401 not really. The builder pattern usually applies to class-based languages like Objective-C or Java, where you use a mutable object as a factory for an immutable one. All of that is redundant in Swift where you have compound value types.
What this allows is so-called "point-free" coding style, where you can set or change properties of structs without creating intermediate variables.
Why the protocol? Since the struct uses vars you can simply write a couple of global functions that mutate using WritableKeyPaths.