Skip to main content

Evaluation Order and Effects

What this concept is​

An expression may produce a value and an observable effect. Reordering, duplicating or omitting evaluation can preserve a mathematical expression while changing a program. Purity is a claim about permitted observations, not about short syntax.

Why it matters here​

Patterns and wrappers often change when functions run. A refactor that fetches configuration early, retries a callback or captures mutable state can change behavior despite returning the same result in a happy-path test.

Concrete example​

In Python, False and probe() does not call probe; evaluating saved = probe() before the expression does. Similarly, value = fetch(); value + value calls fetch once, whereas fetch() + fetch() calls it twice. If fetch advances an iterator or performs I/O, these are not generally interchangeable.

For a closure, consider rate = [2] and lambda x: x * rate[0]. After setting rate[0] = 3, calling with 10 returns 30. To promise the earlier numeric value, capture that integer in a separate binding and avoid later rebinding it. Copying an outer container does not by itself remove nested aliases.

Common confusion / misconception​

A function that returns a fresh object can still read a clock or mutate an argument. Frozen dataclasses do not make arbitrary contained objects immutable. Inlining a function is not safe merely because the printed arithmetic looks equivalent.

How to use it​

Write a trace of visible calls and outputs, then compare it before and after a proposed transformation. Identify exceptions and short-circuit branches. Keep pure calculations separate from effects so most tests need no external dependencies, while retaining boundary integration tests.

Check yourself​

If probe() raises an exception, does moving it out of a false short-circuit branch preserve behavior? No. Does checking equal numeric outputs reveal the changed call count? Not reliably.

Mini drill or application​

Use a callback that appends to a list, then returns 1. Compare calling once and reusing the value with calling twice. Submit both numeric results and call traces. Extend the callback to raise on its second call and explain the difference. Preserve the actual trace instead of inferring it from the final number.

Read this only if stuck​

Use selective reading from Cornell’s data types, MIT representation invariants, and Python dataclasses for their respective claims. Revisit the existing SICP reference for evaluation and environments; no full-book assignment is added. Continue with the construction clinic.