BasicsDecCodeStory_Expressions.html
copyright © James Fawcett
Revised: 05/11/2026
1.0 Prologue
The declarative style begins with a shift in perspective: instead of describing a
sequence of state-changing steps, a program describes relationships between values.
Expressions produce values; they do not modify existing ones. This chapter covers
the four foundational properties that make declarative code compositional and
predictable.
1.1 Pure Expressions
A pure expression computes a value with no observable side effects.
It does not modify variables, perform I/O, or depend on mutable global state. Given
the same inputs it always produces the same output.
-- Haskell: all expressions are pure by default
area r = pi * r * r -- pure; no side effects possible
-- Rust: expressions are pure when they don't use mut, I/O, or unsafe
let area = std::f64::consts::PI * r * r;
-- Impure (imperative): reads external mutable state
let area = PI * r * r; // but PI might be a mutable static in C
Haskell enforces purity at the type level: a function that performs I/O must return
a value wrapped in the IO type. Side-effecting code is structurally
separated from pure code, making the boundary visible in every type signature.
1.2 Immutable Values
In declarative languages, binding a name to a value is not assignment
in the imperative sense - the binding cannot be updated. A new value requires a
new binding.
-- Haskell: no mutable variables; all bindings are permanent
let x = 5
let y = x + 1 -- y is 6; x is still 5, unchanged
-- F#: let bindings are immutable by default
let x = 5
let y = x + 1
// x <- 6 -- compile error; x is not mutable
-- Rust: let bindings are immutable by default
let x = 5;
let y = x + 1;
// x = 6; -- compile error without mut
-- Python (convention): immutable-by-practice via tuples and frozen types
x = 5
y = x + 1
# x = 6 -- legal but violates the declarative discipline
Immutability eliminates a large class of bugs: race conditions on shared state,
aliasing surprises, and unintended mutation through references all become impossible
when values cannot change.
1.3 Value Semantics
Under value semantics, equality is determined by content, not by
identity (memory address). Copying a value produces an independent duplicate; the
copy shares no state with the original.
-- Haskell: structural equality via Eq type class
let p1 = (3, 4)
let p2 = (3, 4)
p1 == p2 -- True; compared by content, not address
-- F#: structural equality is the default for records and tuples
let p1 = { X = 3; Y = 4 }
let p2 = { X = 3; Y = 4 }
p1 = p2 // true
-- Contrast with reference semantics (Java/C# class):
// new Point(3,4) == new Point(3,4) -- false (different objects)
Value semantics makes programs easier to reason about locally: a function that
receives a value cannot affect the caller’s copy. This is why purely functional
languages default to value semantics for all data types.
1.4 Referential Transparency
An expression is referentially transparent if it can be replaced
anywhere in a program by its value without changing the program’s meaning.
This property is the formal definition of “no side effects.”
-- Haskell: referentially transparent
square x = x * x
-- These are equivalent; we can substitute freely:
square 5 + square 5 -- 50
let s = square 5 in s + s -- 50; identical meaning
-- Not referentially transparent (imperative):
// int counter = 0;
// int next() { return ++counter; }
// next() + next() -- result depends on call order (1+2 = 3)
// let n = next(); n + n -- different result (1+1 = 2)
Referential transparency enables equational reasoning: you can
prove program properties by substituting equals for equals, just as in algebra.
It also enables memoization and common-subexpression elimination as safe automatic
optimizations.
1.5 Epilogue
Pure expressions, immutable values, value semantics, and referential transparency
form the bedrock of declarative programming. Together they make programs compositional:
any expression can be understood in isolation, assembled with others, and reasoned
about algebraically. The next chapter shows how types structure and constrain the
universe of values a declarative program manipulates.
1.6 References
Haskell Tutorial - Functions
F# Values - Microsoft
Referential Transparency - Wikipedia
Value Semantics - Wikipedia