Contents:
- A description of a situation I've found myself in enough times to write here about it.
- My way of handling it.
- The question "Have I invented this hacky solution because I'm missing something obvious?".
- 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)
}
}
infix operator .= : AdditionPrecedence
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.