Evaluation
We have seen two philosophies. One says: things change. The other says: new things arise. Now we ask the practical question: when does each serve us?
This is not a debate with a winner. It is an evaluation---a careful weighing of trade-offs. Different problems favor different approaches.
The Question of Memory
The most immediate concern is memory. If we never modify anything, don't we accumulate infinite copies?
Consider our chess game. In the mutable approach:
One board exists
Memory: 1 boardIn the immutable approach, after 40 moves:
state_0, state_1, state_2, ... state_40
Memory: 41 boards?This seems damning. Forty-one times the memory! But the reality is more nuanced.
Structural Sharing
When we derive state_1 from state_0, how much actually changes? In a chess move, typically:
A clever implementation doesn't copy those 62 unchanged squares. It shares them. Both state_0 and state_1 point to the same underlying data for everything that didn't change.
- Two squares change (source becomes empty, destination gets piece)
- Turn changes
- 62 squares remain exactly the same
state_0 and state_1 share:
- 62 unchanged squares
- All piece definitions
- Game rules
state_1 has its own:
- 2 changed squares
- turn valueThe memory overhead shrinks dramatically. Instead of 41 complete boards, we have one "base" plus small deltas for each change.
The Question of Undo
What happens when a player wants to take back a move?
Undo in the Mutable World
In the mutable approach, the previous state is gone. To support undo, we must explicitly save history:
history = []
before each move:
history.push(copy of current board)
to undo:
current board = history.pop()We end up copying anyway! The mutable approach, to support undo, must become partially immutable.
Undo in the Immutable World
In the immutable approach, undo is trivial:
states = [state_0, state_1, state_2, ...]
current = states[2]
to undo:
current = states[1] // just point to the previous stateThe previous states were never destroyed. Undo means pointing to an earlier state. Nothing is copied; nothing is reconstructed.
The Question of Concurrency
What happens when two processes try to modify the board at the same time?
Concurrency in the Mutable World
Imagine two players, Alice and Bob, both try to move at once:
Alice: move knight g1 to f3
Bob: move pawn e7 to e5
Time 1: Alice reads g1 (knight)
Time 2: Bob reads e7 (pawn)
Time 3: Alice writes g1 = empty
Time 4: Bob writes e7 = empty
Time 5: Alice writes f3 = knight
Time 6: Bob writes e5 = pawnThis might work---or it might not. What if Alice's move depended on reading e7? What if the writes interleave differently? The result is unpredictable.
Mutable systems require careful coordination: locks, semaphores, transactions. Complexity grows.
Concurrency in the Immutable World
In the immutable approach, Alice and Bob each receive state_0. They each derive a new state:
state_0 -> (Alice's move) -> state_1a
state_0 -> (Bob's move) -> state_1bNo conflict! They're not modifying the same thing. They're each creating something new from a shared, unchanging origin.
The question of "which state wins" remains---but it's a logical question, not a technical catastrophe. Neither computation corrupted the other.
The Question of Reasoning
Can we understand what our program does?
Reasoning About Mutable Programs
In the mutable world, a function might:
- Modify its inputs
- Modify global state
- Behave differently depending on when you call it
To understand what make_move(board, move) does, you must trace through the code, tracking every modification. You must consider what else might be modifying board simultaneously.
Reasoning About Immutable Programs
In the immutable world, a function:
- Cannot modify its inputs (they're immutable)
- Returns a new value
- Given the same inputs, always returns the same output
To understand what make_move(state, move) does, you look at its definition. That's all. The function is a pure relationship between input and output.
The Evaluation
| Concern | Mutable | Immutable |
|---|---|---|
| Memory (naive) | Lower | Higher |
| Memory (with sharing) | Similar | Similar |
| Undo | Requires extra work | Built-in |
| Concurrency | Complex | Safe |
| Reasoning | Difficult | Straightforward |
| Familiarity | Intuitive | Requires learning |
Neither approach is universally better. The choice depends on what you're building, what guarantees you need, and what costs you can afford.
But now you understand the trade-off. You can make the choice deliberately, not by accident.
We have completed our exploration of the two philosophies of change. We have seen how they differ, what each offers, and what each costs.
In Part III, we turn to a new question: how do we name the patterns we've discovered? How do we capture them, reuse them, build upon them?
We turn to the power of names.