A pure function computes its result from its inputs and leaves externally observable state unchanged. Given the same inputs, it produces the same output. This predictability is called referential transparency: a call can be replaced by its result without changing the program’s behavior. section 4.1 section 4.1.2
Purity is more than returning a value. Reading shared state makes the result depend on something the caller did not supply; changing an object passed by the caller creates a side effect. Console output, filesystem access, network writes, and randomness also break the rule. A function may use values from a closure when the enclosing function follows the same purity rules. The aim is not to prohibit useful effects, but to keep them apart from the logic that transforms data. section 4.1.2 section 4.3.1
Make a dependency visible
Consider `assemble(keys)`, which joins properties read through `this`. Its result depends on an assumed calling context. Changing its signature to `assemble(keys, obj)` makes that dependency an argument: `keys.map(f => obj[f]).join('')`. Neither version needs to change the object, but the second is easier to reason about and compose because the required object is explicit. Local variables in a hash calculation are not themselves the problem; dependence on an implicit external context is. section 4.3.2
Pure functions fit together as a data flow: one function’s output becomes another’s input. Array `filter` and `map` can select and transform values while leaving the original array intact; a subsequent `reduce` produces a result. This avoids surprises when another part of an application still holds the original array. By contrast, in-place operations such as `reverse` and `sort` can change shared data. section 4.1 section 4.3
const computeBalance = address =>
compose(
Money.round,
reduce(Money.sum, Money.zero()),
map(balanceOf(address)),
flatMap(prop('data')),
filter(
compose(
not,
prop('isGenesis')
)
),
Array.from
);
The balance calculation is expressed as a composition: select ledger blocks, extract transactions, map them to balance changes, sum them, and round the result. section 4.2
Start a refactor by splitting a large operation into small tasks and examining each task’s inputs and effects. Hash generation, for example, separates assembling object data from computing its digest; currying supplies hash options before those operations are composed. The surrounding method can still know about its transaction object while the functions doing the calculation receive the data they need. Purity does not require abandoning objects. section 4.3.2 section 4.4.2 section 4.8
Real applications still read files and make requests. Put those operations at a boundary and compose them with pure transformations. A word-counting pipeline, for instance, reads a file, decodes it, splits its contents, and counts the words; reading is effectful, while the data-processing steps can remain separate. This division makes critical logic easier to test, reuse, and reason about without pretending the entire application is pure. section 4.3.1 section 4.8


















