Dec Code Story

Chapter #1 – Expressions & Values

pure expressions, immutable values, value semantics, referential transparency

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