F2 · Functional Programming
Pure functions & side effects
A pure function depends only on its arguments and changes nothing outside itself. Same input, same output, every time — and the world is untouched. That single property is what makes functional code easy to reason about.
F1.02 met this idea as referential transparency: a call can be replaced by its result. Here we take the practitioner’s view. A pure function is deterministic and effect-free; a side effect is any reach beyond the return value — printing, reading the clock, drawing a random number, mutating shared state. Real programs need effects, so the craft is isolating them: a pure core wrapped in a thin effectful shell.
Same in, same out
The test for purity is simple: call the function twice with the same argument. A pure function returns the same value both times, because its result is a function of its input alone. If the two calls can differ, something outside the arguments is leaking in — and the function is not pure.
Call each function twice with the same input and compare.
- pure
- output depends only on the arguments; nothing outside is changed.
- side effect
- any observable interaction beyond the return value — I/O, randomness, time, mutation.
- deterministic
- the same input always yields the same output.
- effect boundary
- the thin edge of a program where effects are allowed to happen.
Call it twice · do the results agree?
F1.02 · substitution
A referentially transparent expression can be replaced by its value.
F2 · purity
That replacement is only safe when the call is pure — deterministic and effect-free.
Determinism is the observable face of purity. If two identical calls can disagree, the result depends on more than the input.
What counts as an effect
An effect is any way a function touches the world beyond returning a value: writing output, reading the clock or the filesystem, drawing randomness, or mutating state others can see. Operations that only read their arguments and return a result are pure. The same code can be either — transforming a string is pure; printing it is an effect.
Inspect each operation.
Pure or effect?
Algebra
A function maps inputs to outputs — nothing more is observable.
Elixir
I/O, :rand, DateTime, and message passing are effects; transforms over data are pure.
The tells are I/O, randomness, time, and shared mutation. A function that avoids all four, and reads only its arguments, is pure.
Core and shell
Programs need effects, so functional design does not ban them — it corrals them. Push the effects to the edges: read input at the start, write output at the end, and keep everything between pure. That pure middle is the functional core; the effectful edge is the imperative shell. The core is deterministic, so it is easy to test and safe to reuse.
Step a value through a small program and watch where the effects sit.
Functional core, imperative shell
Principle
Keep the decision-making pure; let only the edges talk to the world.
Elixir
Pure functions in the middle of a pipeline; IO, files, and sockets at the ends.
Two effect steps at the edges, three pure steps in the core. The core can be tested with plain values — no input to stub, no output to capture.
What this lands
Purity is one rule with large consequences: a function’s output depends only on its arguments, and it changes nothing outside itself. That makes calls deterministic, lets you replace a call with its result as in F1.02, and keeps the reasoning local. Effects — I/O, randomness, time, mutation — are real and necessary, so the discipline is to isolate them in a thin shell around a pure core. The next module, F2.02, looks at the data that core works on: immutable values made cheap by structural sharing.
Next: F2.02 — Immutability & persistent data — why copying an immutable value is cheap. The full chapter path is on the F2 overview.