jonnifyelixir

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?

call 1 9 = call 2 9


      
square(3) twice → 9 and 9 · equal — pure, referentially transparent

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?

String.upcase("hi") pure


      
pure · depends only on its argument and returns a value

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

1 / 5
PURE CORE · TESTABLE read_line() # effect · shell "42" String.to_integer(line) # pure 42 double(n) # pure 84 to_string(result) # pure "84" IO.puts(text) # effect · shell prints "84"
step 1 · read_line() (effect) · reads "42" from input

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.