Evaluation

Derivation

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:

python
    One board exists
    Memory: 1 board

In the immutable approach, after 40 moves:

python
    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 value

The 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:

wollok
    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:

python
    states = [state_0, state_1, state_2, ...]
    current = states[2]
    
    to undo:
        current = states[1]  // just point to the previous state

The 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:

python
    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 = pawn

This 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:

haskell
    state_0 -> (Alice's move) -> state_1a
    state_0 -> (Bob's move)   -> state_1b

No 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

ConcernMutableImmutable
Memory (naive)LowerHigher
Memory (with sharing)SimilarSimilar
UndoRequires extra workBuilt-in
ConcurrencyComplexSafe
ReasoningDifficultStraightforward
FamiliarityIntuitiveRequires 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.