liveBook · Manning

Pure function

As explained in Functional Design and Architecture: Examples in Haskell

A pure algorithm is an algorithm whose result is determined by its inputs and which produces no observable side effects.

A pure function is a computation that uses its arguments to produce a result without observable side effects or mutation of state. Calling it again with the same arguments returns the same result, making its behavior deterministic. section 1.5.1

Purity is a practical restriction on shared mutable state. Mutation and shared state can make concurrent code nondeterministic; keeping data transformations pure removes that class of parallel bugs. Pure functions also compose: combining pure functions produces another pure function, so a layered calculation remains predictable at every level. section 1.5 section 1.5.1

A predictable calculation pipeline

A calculation can add two inputs, compute their average, and return the larger of that average and a third input. If every step is pure, the same three inputs always yield the same final value. This makes the evaluation flow easy to follow and separates the calculation from any later printing, storage, or other effect. section 1.5.1

def max(a: Float, b: Float) : Float = {
 math.max(a, b)
}
  
def calc(a: Int, b: Int, c: Float) : Float = {
 val sum = a + b
 val average = sum / 2
 max(average, c)
}

Inspect a small composition of pure `max` and `calc` functions: values flow through arguments and return values without state changes or effects. section 1.5.1

Effects need not disappear; they can be kept at a boundary. A pure `max` operation can calculate a value, while a separate `printNumber` routine performs output. In languages with effect types, such as Haskell and PureScript, a function type can distinguish an ordinary result from an `IO` or `Effect` action. In languages without that separation, a function with an ordinary result type may still invoke effects, so purity relies more on design discipline. section 1.5.1

Purity improves testing by making a function's result depend only on supplied test data:

  • Test named input-output cases directly, including edge cases such as zero, without preparing an external environment. section 15.1.1
  • Keep tests independent: shared state can create ordering dependencies and race conditions during parallel execution. section 15.1.1
  • Express invariants as properties for small pure algorithms, then generate inputs automatically and inspect minimized counterexamples. section 15.1.2

Testing a square algorithm

Example-based tests check that squaring positive, negative, and zero values gives expected outputs. A property test can instead assert that the result equals `x * x` for generated integers. Generated cases broaden coverage, but passing a limited run does not prove the algorithm has no subtle bug: an implementation that fails only beyond the generated range may still pass. section 15.1.1 section 15.1.2

spec :: Spec
spec = prop "square" $                   #1
  \x -> (malformedSquare x == x * x)     #2
  
-- Test output:
> Falsified (after 2 tests): 1

See a property that exposes an intentionally malformed square function after generated input violates the squaring invariant. section 15.1.2

Purity does not mean every program operation is pure. It gives algorithms a stable, composable core; effectful operations still require isolation and careful control. That separation supports predictable behavior, clearer evaluation flow, and tests that focus on logic rather than environment setup. section 1.5.1 section 15.1.1

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Defines purity through side effects, mutation, determinism, composition, effect types, and testing practices without formal semantics.
Examples
Uses a `calc` and `max` pipeline, Haskell IO routines, square tests, IORef-dependent tests, and malformed square properties.
What you can do after
Apply it via hspec and QuickCheck to test deterministic algorithms with cases and generated invariants.
How it connects
Connects purity with immutability, determinism, shared mutable state, concurrency, effect types, property-based testing, and test isolation.

Also explained in

Compare how 19 books explain pure function →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.