Skip to content

Instantly share code, notes, and snippets.

@algal
Last active October 26, 2017 09:01
Show Gist options
  • Save algal/b4d546d6cbc556f264fb5ee8112b7ac8 to your computer and use it in GitHub Desktop.
Save algal/b4d546d6cbc556f264fb5ee8112b7ac8 to your computer and use it in GitHub Desktop.
Brief exploration of Swift classes that assign to self via protocol extensions
/*
## 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