Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Withable.swift
/// 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(&copy)
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
@elfenlaid

This comment has been minimized.

Copy link

elfenlaid commented Jan 29, 2019

Awesome, but I can't quite get why there is an empty init() in Withable protocol?
Ah, nvm, it's used self.init() here

@alessandro-martin

This comment has been minimized.

Copy link

alessandro-martin commented Feb 8, 2019

Why the protocol? Since the struct uses vars you can simply write a couple of global functions that mutate using WritableKeyPaths.

@tncowart

This comment has been minimized.

Copy link

tncowart commented Feb 8, 2019

This is one of the things Lenses can do.

@joelkraut

This comment has been minimized.

Copy link

joelkraut commented Feb 8, 2019

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.

@P0ed

This comment has been minimized.

Copy link

P0ed commented Feb 11, 2019

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(&copy)
	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 { ... })

@P0ed

This comment has been minimized.

Copy link

P0ed commented Feb 11, 2019

Basically it converts (inout A) -> Void to (A) -> A function

@dcutting

This comment has been minimized.

Copy link

dcutting commented Feb 11, 2019

Nice Nick, but now you've got me wondering what interesting project you're using this in... :)

@wsof401

This comment has been minimized.

Copy link

wsof401 commented Feb 13, 2019

it seems like builder pattern?

@nicklockwood

This comment has been minimized.

Copy link
Owner Author

nicklockwood commented Feb 19, 2019

@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).

@nicklockwood

This comment has been minimized.

Copy link
Owner Author

nicklockwood commented Feb 19, 2019

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.