# Nice way of copying an immutable value while changing only a few of its many properties?

Contents:

1. A description of a situation I've found myself in enough times to write here about it.
2. My way of handling it.
3. The question "Have I invented this hacky solution because I'm missing something obvious?".
4. A happy observation about improvements in optimization in a recent snapshot.

So here's the situation. Let's say there's this thing `Foo`:

``````struct Foo {
var clasterId: String
var nanoHopplerLength: Double
var flapsterity: Float
var isSingleWave: Bool
var zapsterCount: Int
}
``````

And we have `someFoo`:

``````let someFoo = Foo(clasterId: "abc",
nanoHopplerLength: 1.23,
flapsterity: 45.6,
isSingleWave: true,
zapsterCount: 7)

``````

Now we happen to need `anotherFoo` that is just like `someFoo`, except for
`zapsterCount` which should be incremented by `8` and
`isSingleWave` which should be `false`.

How do we do that?

Alternative 1

``````let anotherFoo1 = Foo(clasterId: someFoo.clasterId,
nanoHopplerLength: someFoo.nanoHopplerLength,
flapsterity: someFoo.flapsterity,
isSingleWave: false,
zapsterCount: someFoo.zapsterCount + 8)
``````

That's very verbose, and it would be even worse if `Foo` had more properties and we only needed one of them to change. (Now it's at least 2 out of 5, could be eg 1 out of 7!)

Alternative 2

``````var tmpMutableFoo = someFoo
tmpMutableFoo.zapsterCount = someFoo.zapsterCount + 8
tmpMutableFoo.isSingleWave = false
let anotherFoo2 = tmpMutableFoo
``````

Perhaps a little better, but now we have that temporary mutable copy of `someFoo` in our scope. We could get it out of the scope using an immediately invoked closure:

Alternative 3

``````let anotherFoo3: Foo = {
var tmf = someFoo
tmf.zapsterCount = someFoo.zapsterCount + 8
tmf.isSingleWave = false
return tmf
}()
``````

But that's not very nice either.

It feels like I'm missing some super obvious simpler way (am I? ), I mean something similar to but better than eg:

Alternative 4

``````let anotherFoo4 = someFoo
.setting(\.zapsterCount, to: someFoo.zapsterCount + 8)
.setting(\.isSingleWave, to: false)
``````

or

Alternative 5

``````let anotherFoo5 = someFoo .= (\.zapsterCount, someFoo.zapsterCount + 8)
.= (\.isSingleWave, false)
``````

which is possible with this:

``````protocol KeyPathSettable {}
extension KeyPathSettable {
func setting<V>(_ keyPath: WritableKeyPath<Self, V>, to value: V) -> Self {
var result = self
result[keyPath: keyPath] = value
return result
}
static func .=<V>(lhs: Self, rhs: (WritableKeyPath<Self, V>, V)) -> Self {
return lhs.setting(rhs.0, to: rhs.1)
}
}

extension Foo : KeyPathSettable {}
``````

Btw, I noticed that with a recent snapshot (2020-01-21) of the compiler (and -O), all alternatives seem to compile down to identical binary code (at least in my quick test). The default toolchain of Xcode 11.3.1 (11C504) does not, however.

2 Likes

Why should the mutable copy be temporary? Why not leave it mutable?

Once it gets lowered to SSA, they're all going to look like this anyway, so you won't get better performance than this one.

I'm not sure I understand what you mean. If you mean I should leave `tmpMutableFoo` mutable, then well, that is Alternative 2, which is fine, unless you feel that it shouldn't be necessary to declare a new variable every time you need to do this kind of thing (assuming all copy-with-little-modifications are not of the same type, so the mutable variable cannot be reused).

If you mean why I wouldn't want to simply do this:

``````var anotherFoo2b = someFoo
anotherFoo2b.zapsterCount = someFoo.zapsterCount + 8
anotherFoo2b.isSingleWave = false
``````

then, depending on how `anotherFoo2b` is used, it's the same as asking "why ever use `let` and not just use `var`?".

Something like this has been discussed here before, under the name βwithβ, but thatβs probably impossible to search for.

3 Likes

Ah, I actually called it `with(:)` and `Withable` when I first wrote it.

Trying to search the forums for Withable, I found this:

Yes, that's what I mean. Your problem was:

Now we happen to need `anotherFoo` that is just like `someFoo` , except for
`zapsterCount` which should be incremented by `8` and
`isSingleWave` which should be `false` .

So what you should do is make a copy of `someFoo`, called `anotherFoo`, increment `zapsterCount` by 8 and assign `isSingleWave` to false. Just like you wrote it, basically.

I don't buy in to all this `with` and fancy syntax sugar. There are some who insist on shoving functional programming in to any and every hole, even for the very simplest of problems. I'm more of the "right tool for the right job" kind of person, and in this case there's absolutely nothing wrong with the obvious solution. There is also such a thing as over-engineering...

There is a use-case for `let`: when you initialise a variable and never change it. You initialised a variable, but then changed a couple of properties. It's a `var`

1 Like

Do you think there is any difference in the benefit that could be gained by changing `var` to `let` in

``````var x = foo()
// Never mutate x
``````

compared to in

``````var x = foo()
mutate(&x)
// Never mutate x after this point
``````

(where the change in the second would involve adding a `with` or other construct to allow the initial mutation)

To me, the change has equal benefit in both, so my answer to "why not just leave it a `var`" is the same as the answer to "why use `let` at all"

If you disagree, I'm curious as to what benefit the use of `let` is getting you in the first case that it wouldn't also get you in the second (aside from "the compiler stops complaining at me")

2 Likes

Hmm I think I remember seeing it somewhere else closer to

``````func with<T>(_ t: T, _ body: (inout T) throws -> ()) rethrows -> T {
var t = t
try body(&t)
return t
}
``````

which would allow you to do

``````let anotherFoo6 = with(someFoo) {
\$0.zapsterCount += 8
\$0.isSingleWave = false
}
``````
4 Likes

OK, so we agree that there is a use-case for `let`. And that's exactly the use-case I describe:
The `let` in
`let anotherFooX = ...`
implies that `anotherFooX` should never change after the initialization.
Here's the first alternative again:

Clearly, the use-case here is to

I have the same kind of requirements and this is the best solution I've found so far. It would be better if we could extend an arbitrary type in Swift (so we could define `with` as a method), but that's it for now.

But we can extend an arbitrary type in Swift so that it gets `with` as a method, ie:

``````protocol CopyableWithModification {}
extension CopyableWithModification {
func with(_ modify: (inout Self) throws -> Void) rethrows -> Self {
var copy = self
try modify(&copy)
return copy
}
}
extension Foo : CopyableWithModification {} // (<-- Foo can be any type.)
``````

which will let us write:

``````let anotherFoo7 = someFoo.with { \$0.zapsterCount += 8
\$0.isSingleWave = false }

``````
(I've included the equivalent Alternative 1 here, for comparison.)
``````let anotherFoo1 = Foo(clasterId: someFoo.clasterId,
nanoHopplerLength: someFoo.nanoHopplerLength,
flapsterity: someFoo.flapsterity,
isSingleWave: false,
zapsterCount: someFoo.zapsterCount + 8)
``````

Anyway, I'm just saying that any type can be extended like this to get `with` as a method. But perhaps that's not what you meant?

Not that hard to find it if you remember who wrote the post: Circling back to `with`

3 Likes

I meant something a little different, that is, "extension" as in "adding a method to an arbitrary type", rather than "defining a protocol and making an arbitrary type conform to it".

For example, in pseudo-swift:

``````extension Any {
func with(_ modify: (inout Self) throws -> Void) rethrows -> Self {
var copy = self
try modify(&copy)
return copy
}
}
``````

This would add a `with` method to everything, without the need to define a protocol and manually conforming types to it. I understand that writing `extension Foo : CopyableWithModification {}` for each type doesn't require much effort, but I still find easier and faster to just use a global `with` function.

2 Likes

3:30am so not much brain left "today", but the OP question made me think of:
make an initializer for the struct that takes an instance of self as the first param and then all the normal `init` params as optionals with default value of nil then you could create a new one with zero or more params modified.

Bit verbose to create, but nice to use once you have it (macOS Swift playground, Swift 5.1):

``````import Cocoa

struct Foo {
var name: String
var age: Int
var height: Double

// unfortunately if you make one custom init you lose the auto-generated one
// so we have to write that as well:
init(name: String, age: Int, height: Double) {
self.name = name
self.age = age
self.height = height
}

// initialize from an existing instance with optional customization of any property
init(from: Foo, name: String? = nil, age: Int? = nil, height: Double? = nil) {
self.init(name: name ?? from.name,
age: age ?? from.age,
height: height ?? from.height)
}
}

// make a Foo:
let a = Foo(name: "Joe", age: 20, height: 1.5)
print(a)

// simple copy; b = a is easier :)
let b = Foo(from: a)
print(b)

// copy but change one param, age.
let c = Foo(from: a, age: 10)
print(c)

// change two params
let d = Foo(from: a, age: 10, height: 1.3)
print(d)
``````

output is:

Foo(name: "Joe", age: 20, height: 1.5)

Foo(name: "Joe", age: 20, height: 1.5)

Foo(name: "Joe", age: 10, height: 1.5)

Foo(name: "Joe", age: 10, height: 1.3)

Yes, that was the first solution I went for (a long time ago), but since it involves so much typing for each type I wanted to use it on, I started using something like what I described in the OP, and since then I've changed to this:

``````protocol CopyableWithModification {}
extension CopyableWithModification {
func with(_ modify: (inout Self) throws -> Void) rethrows -> Self {
var copy = self
try modify(&copy)
return copy
}
}
``````

Having that protocol means we'll only need to write one line for any type we want to use it on:

``````extension Foo : CopyableWithModification {}
``````

and the call site actually becomes less verbose than the init-method too, eg:

``````let anotherFoo = someFoo.with { \$0.zapsterCount += 8
\$0.isSingleWave = false }
``````

compared to:

``````let anotherFoo = Foo(from: someFoo,
zapsterCount: someFoo.zapsterCount + 8,
isSingleWave: false)
``````

This method requires mutable properties though, which the init-method doesn't.

1 Like

initialise a variable and never change it.

The same also applies to auto generated member `init()`, in order to make use of it, you must declare the member field as `var`, cannot be `let`:

``````struct Blah {
let a: Int

// you have to write the init
init(_ a: Int) {
self.a = a
}
}
``````

But if you declare field as `var`, no need to write the `init()`:

``````struct Blah {
var a: Int
}
``````

Can we not have both? declare `let a: Int` and make use of auto gen'ed `init()`

That's not correct. `var` and `let` variables are part of the auto-generated initializer, but when you added your own `init` inside the type declaration, the auto-generated initializer was lost. Moving your `init` to an extension will let you keep both.

3 Likes

You are right, my example is no good. I did encountered this problem with some SwiftUI something `Binding` such that I cannot declare it as `let` and use auto gen'ed `init()`...if I can find my code, I'll post.

Yes, `@Binding` annotated variables must be `var` due to how property wrappers work. How that behavior interacts with the auto-generated `init` I'm not sure.

I remember now: its `let` with default value that can be overridden but using only the auto gen'ed `init()`:

``````struct Blah {
let a = 100
}

let x = Blah(a: 999).   // <== error: argument passed to call that takes no arguments
``````

but it's okay if you use `var`:

``````struct Blah {
var a = 100
}

let x = Blah(a: 999)
``````

But I really want this:

``````struct Blah {
let a: Int

init(a: Int = 100) {
self.a = a
}
}

let x = Blah(a: 999)
``````

So I must write the `init()`

(I remembered Binding because I didn't like having to hand code the `init()` and deal with setting the bindings)