One of the biggest issues I have had with enum cases is ensuring in-place mutation. You have to spell things in a very particular way to avoid making extra copies:
struct TreeNode {
enum Storage {
case internal(children: [TreeNode])
case leaf(values: [Int])
}
var _storage: Storage
mutating func insert(value: Int) {
switch _storage {
// The optimizer recognizes this magic incantation and eliminates redundant copies.
case .leaf(var values) { // 1: capture payload as a var
values.append(value) // 2: mutate var
_storage = Storage.leaf(values) // 3: assign a new value of the same enum case with the mutated payload
}
case .internal(var children) {
// appropriate recursive logic here
}
}
}
}
In the past, I’ve seen suggestions of an inout
capture specifically targeted at this use case. But upon further consideration, if enums supported mutating methods like structs do, this entire pattern could be avoided by reusing a concept one has already learned. Borrowing the syntax from structs, the above example could be rewritten:
indirect enum TreeNode {
case leaf {
/// The values stored in this leaf node.
/// - Note: Look, DocC comments!
var values: [Int]
mutating func insert(value: Int) {
values.append(value)
}
}
case internal {
var children: [TreeNode]
mutating func insert(value: Int) {
// appropriate recursive logic here
}
}
}
Of course, without turning each case into a nominal type, this would require all cases carry the same set of instance methods. While it is appealing to turn each case into a nominal type, it’s not clear how one would invoke case-specific methods in practice:
indirect enum TreeNode {
case child {
var values: [Int]
mutating func shuffleValues() { ... }
}
case internal {
var children: [TreeNode]
}
}
func doSomething(with aNode: TreeNode) {
switch aNode {
case child {
child.shuffleValues() // error: aNode is still statically typed TreeNode here
}
}
}
We could contemplate a rebinding case let
syntax for use within switch
, but this increased scope is all superfluous to the original goal of making it convenient and straightforward to implement in-place mutations on enums, even if those operations have to be well-defined for all cases.