The Case Against Rebinding
Why reusing variable names undermines clarity — and how immutable bindings produce better code.
In many languages, variables can be rebound — assigned a new value after their initial definition. While convenient, this is almost always a sign of unclear thinking and leads to confusing code.
The Problem with Rebinding
Consider tracking the weight of a carrot:
weight = 150 // ... later, after taking a bite ... weight = 142
This compiles in many languages, but it obscures what's actually happening. The weight after a bite isn't the same thing as the weight before. By reusing the name weight, we've hidden an important distinction.
What's Actually Happening
When you take a bite from a carrot, you have two distinct values:
- The original weight (before the bite)
- The remaining weight (after the bite)
These are two different values representing two different states. Giving them the same name hides this reality.
The Better Approach
Use descriptive names that reflect what each value actually represents:
original_weight = 150 bite_size = 8 remaining_weight = original_weight - bite_size
Now the code documents itself. Anyone reading it understands:
- There was an original weight
- Something was removed (a bite)
- We calculated what remains
Why This Matters
1. Debugging Becomes Easier
weight = 150 weight = weight - 8 weight = weight - 12 // What was the original weight? We've lost it.
original_weight = 150 after_first_bite = original_weight - 8 after_second_bite = after_first_bite - 12 // All values are preserved and traceable.
2. Code Reflects Reality
A 150g carrot that becomes 142g didn't have its weight "reassigned" — it lost material. Your code should reflect that distinction.
3. Concurrent Code is Safer
When values don't change, there's no risk of one part of your program seeing an old value while another sees a new one. Immutability eliminates entire categories of concurrency bugs.
A More Complex Example
Consider tracking a bank account:
balance = 1000 balance = balance - 50 // withdrawal balance = balance + 200 // deposit balance = balance - 75 // another withdrawal // What happened? Hard to tell.
initial_balance = 1000 after_withdrawal = initial_balance - 50 after_deposit = after_withdrawal + 200 final_balance = after_deposit - 75 // Clear history of transformations.
Or even better, model the transactions explicitly:
initial_balance = 1000 transactions = [-50, 200, -75] final_balance = initial_balance + sum(transactions)
The Rule
If you find yourself rebinding a variable, stop and ask:
- Am I dealing with two conceptually different things?
- Would a new name make the code clearer?
- Am I trying to model state changes? (Consider a different approach)
The answer is almost always "yes" to at least one of these questions.
How Stone Handles This
Stone takes this principle to its logical conclusion: once a name is bound to a value, it cannot be rebound. There is no reassignment and no shadowing. When you genuinely need state that evolves over time, Stone provides evolve — a structured loop construct with explicit state transitions using prime notation (x'):
balance = evolve (balance = 1000) for (tx in transactions) {
balance' = balance + tx
}This isn't just a stylistic choice — it's also a performance enabler. Because the compiler knows values never change, it can freely reorder computations, eliminate redundant work, and parallelise operations. Immutability is one of the key reasons Stone achieves C-level performance.
Good names cost nothing but make code dramatically easier to understand, debug, and maintain. Use them generously. And when you need state changes, make them explicit — your future self (and your compiler) will thank you.