Last active
October 26, 2017 09:01
-
-
Save algal/b4d546d6cbc556f264fb5ee8112b7ac8 to your computer and use it in GitHub Desktop.
Brief exploration of Swift classes that assign to self via protocol extensions
This file contains 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
/* | |
## background | |
When you write a function that mutates the var properties of a | |
mutable struct, this _creates a new instance_.[1] As a result, | |
a variable pointing to a value of this type before the mutation | |
will still be pointing to the old value after the mutation, as long | |
as you did not use that variable to do the mutation. | |
When you write a function that mutates the var properties of a class, | |
this _modifies the instance_. As a result, a variable | |
pointing to a value of this type before the mutation will be pointing | |
to the updated value after the mutation, even if you did not use that | |
variable to do the mutation. | |
## surprise! | |
I thought this was a hard and fast difference between structs and classes. | |
But you can use assignment to `self` within a protocol extensions | |
to define a class that behaves as if it a mutable struct, as shown below. | |
Why would you want to do this? As @jckarter says, "immutable class | |
instances are reasonable value type models". | |
What does this mean? An immutable class is a class that does not in itself | |
define any mutable properties (or, presumably, transitively mutable | |
properties). To use it as the "model" of a value type means, I think, | |
to use it as a type which defines the storage underlying a "value type". | |
I think here "value type" may not mean a struct, but a type which behaves | |
as if it has value semantics (so, for instance, not a struct containing a | |
var with a mutable reference type). | |
So this might be the mechanism you need if you want to define elaborate | |
functional data structures, as in Clojure's collections, or as in Functional | |
Data Structures, by Okasaki. | |
// notable thread, starting here: https://twitter.com/JadenGeller/status/840742896718376961 | |
// notable thread, ending here: https://twitter.com/jckarter/status/840791894065991681 | |
[1]: okay, technically, it behaves as if it creates a new instance. | |
*/ | |
import Foundation | |
// A counter that increments | |
protocol SelfReplacingIncrementer { | |
init(i:Int) | |
mutating func inc() | |
var i:Int { get } | |
} | |
// it increments by assiging a new self | |
extension SelfReplacingIncrementer { | |
mutating func inc() { | |
let a = Self(i:self.i + 1) | |
self = a | |
} | |
} | |
// This class will be a counter | |
final class ImmutableClassCounter { | |
init(i ii:Int) { | |
i = ii | |
} | |
let i:Int // <- BEHOLD! The instance is immutable! | |
} | |
// But it's unholy becasue it uses self-assignment from the protocol extension | |
extension ImmutableClassCounter : SelfReplacingIncrementer { } | |
// This class will also be a counter | |
final class MutableClassCounter { | |
init(i:Int) { | |
self.i = i | |
} | |
var i:Int | |
func inc() { | |
self.i = self.i + 1 | |
} | |
} | |
struct StructCounter : SelfReplacingIncrementer { | |
init(i ii:Int) { | |
i = ii | |
} | |
var i:Int | |
} | |
// The NormalClassCounter behaves like a reference type | |
var g = MutableClassCounter(i:0) | |
let gBefore = g | |
g.i | |
g.inc() | |
g.i | |
gBefore.i // => 1, the new value | |
// The struct-based counter behaves like a value type | |
var h = StructCounter(i: 0) | |
let hBefore = h | |
h.i | |
h.inc() | |
h.i | |
hBefore.i // => 0, the old value | |
// But the ImmutableClassCounter is a reference type (a class), but it behaves like a value type (a struct). | |
var f = ImmutableClassCounter(i:0) | |
let fBefore = f | |
f.i | |
f.inc() | |
f.i | |
fBefore.i // => 0, the old value | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment