Overview
Pure functional languages describe computation as expression → value mappings, independent of time or state.
However, real-world programming requires mutation: variables that change, data that updates, and effects that persist.
To reason about this, programming languages extend their semantics with locations and a store (σ).
Note
Mutable state introduces time into semantics — expressions no longer evaluate to a single value, but to a value and a new store.
Core Concepts
Values, Locations, and the Store
- Values — immutable entities (integers, booleans, closures).
- Locations (ℓ) — addresses in memory where values reside.
- Store (σ) — mapping of locations to values:
σ : Location → Value
When an expression mutates state, it returns both a result and a new store:
⟨e, σ⟩ ⇓ ⟨v, σ'⟩
Motivation
Mutable state allows:
- Persistent data (e.g., object fields, variables, arrays)
- Efficient algorithms (in-place updates)
- Controlled I/O and interaction
- Sequencing of dependent computations
But it also introduces:
- Aliasing: multiple references to the same location.
- Order dependence: evaluation results depend on timing.
Syntax
We extend the λ-calculus with reference primitives:
t ::= ...
| ref t -- allocate reference
| !t -- dereference
| t1 := t2 -- assignment
| t1 ; t2 -- sequencing
Informal Behavior
| Construct | Meaning | Example |
|---|---|---|
ref t | allocate a new location containing t | let x = ref 5 |
!t | read the value stored in the location | !x → 5 |
t1 := t2 | update location from t1 with value of t2 | x := 7 |
t1 ; t2 | sequence: evaluate t1, then t2 | x := 3; !x |
Tip
The sequencing operator
;explicitly orders effects, ensuring thatt1is evaluated beforet2.
Operational Semantics
Small-step rules capture how state evolves step by step.
Allocation
⟨ref v, σ⟩ → ⟨ℓ, σ[ℓ ↦ v]⟩ (ℓ fresh)
ref vallocates a new locationℓ.- The store is extended with a mapping from
ℓtov.
Dereference
⟨!ℓ, σ⟩ → ⟨σ(ℓ), σ⟩
- Reading a reference returns the stored value, leaving the store unchanged.
Assignment
⟨ℓ := v, σ⟩ → ⟨unit, σ[ℓ ↦ v]⟩
- Updates the existing mapping at
ℓ. - Returns a
unitvalue to indicate sequencing, not computation.
Sequencing
⟨t1 ; t2, σ⟩ → ⟨t1', σ'⟩ ; t2
until t1 reduces to unit, then continue with t2.
Example
Diagram idea (
store_evolution.svg)
Show σ as a table evolving from{}→{ℓ1: 5}→{ℓ1: 7}as evaluation proceeds:let r = ref 5 in r := 7; !r
Example Walkthrough
Consider:
let x = ref 0 in
x := !x + 1;
!xStep-by-step:
-
ref 0allocates ℓ₁, σ₁ = {ℓ₁ ↦ 0}. -
!xreads σ₁(ℓ₁) = 0. -
x := 0 + 1updates σ₂ = {ℓ₁ ↦ 1}. -
!xnow reads σ₂(ℓ₁) = 1.
Final result:
⟨!x, σ₂⟩ ⇓ ⟨1, σ₂⟩
Tip
This pattern — read, modify, write — is fundamental in imperative programming.
In semantics, it manifests as store-passing between expressions.
Aliasing
Two variables can reference the same location.
Changing one affects the other.
Example
let a = ref 0 in
let b = a in
b := 5;
!aResult:
⟨!a, σ'⟩ ⇓ ⟨5, σ'⟩
because both a and b point to the same ℓ.
Warning
Diagram (
aliasing_effects.svg)
Show both identifiersaandbpointing to one locationℓ1in σ.
Assignment throughbchanges the shared binding.
Aliasing is powerful but dangerous — it couples parts of a program through shared state.
Optimizations and reasoning become harder because order now matters.
Side Effects and Sequencing
Sequential Composition
t1 ; t2
ensures t1 executes before t2, even if t1 returns no useful value.
Example
x := 1;
print (!x);
x := !x + 1;Each step mutates the shared store.
Effects compose through time, not through function return values.
Tip
Effects break referential transparency — two identical expressions may yield different results depending on σ.
Referential Transparency and Effects
| Expression | Without Effects | With Effects |
|---|---|---|
let y = 1 + 1 | always 2 | always 2 |
let y = ref 1 | immutable | allocates new memory |
!r | same result for same r | may differ if store changes |
A pure function f(x) always yields the same result for the same input.
With mutable state, the meaning depends on σ — the state of the world.
Warning
Mutation makes equational reasoning (substitution, simplification) invalid unless effects are controlled or isolated.
Modeling State Formally
We now evaluate expressions as store-transforming computations:
⟦t⟧ : Store → (Value × Store)
In λ-calculus terms, this means:
⟦ref t⟧ σ = let (v, σ') = ⟦t⟧ σ in (ℓ, σ'[ℓ ↦ v])
and similarly for !t and t1 := t2.
Note
This model is the foundation for monadic state in functional languages like Haskell.
Monadic View (Optional Bridge)
Haskell’s State monad captures store-passing explicitly:
type State σ α = σ -> (α, σ)
ref x ↦ newRef x
!r ↦ readRef r
r := v ↦ writeRef r vThis bridges imperative effects and functional purity — making mutations explicit in type signatures.
Common Pitfalls
Warning
Aliasing: accidental shared references lead to unexpected updates.
Order dependence: swapping statements changes meaning.
Memory leaks: lost references keep values alive in σ.
Hidden coupling: side effects across modules break modular reasoning.
Diagram Concepts
-
store_evolution.svg: stepwise state updates through evaluation. -
aliasing_effects.svg: shared location aliasing between identifiers. -
sequencing_flow.svg: sequential control flow over time with σ progression.