Domains and Partial Orders
Operational semantics tells you meaning by running a program: it grinds through a transition
relation, step after step, and the meaning of a command is the trace it produces. Denotational
semantics asks a bolder question. Can we hand each program phrase a mathematical
object — a value, a function, a set — that is its meaning, once and for all, with no
machine to run? For an arithmetic expression the object is easy: a number. For a command it is a
function from states to states. But the moment we allow recursion and
loops, an abyss opens: a program can run forever, and "the function it computes" may be
undefined on some inputs and defined on others. What mathematical object captures a partial,
possibly-nonterminating computation — and, crucially, lets us solve the circular equations that
recursion writes?
The answer, due to Dana Scott and Christopher Strachey
around 1970, is to stop working with bare sets and start working with domains: sets
equipped with an order that records "how much information" a value carries. This single move —
meaning-as-a-function needs order, not just membership — is the foundation of the entire
theory. This page builds the order-theoretic scaffolding: partial orders, the least element
\bot, chains, least upper bounds, complete partial orders, and the
monotone-and-continuous functions that live on them.
Order as information
Think of x \sqsubseteq y — read "x approximates
y" — as the claim that y is at least as
defined as x: it agrees with everything x
already committed to, and perhaps says more. A computation that has not yet produced an answer sits at
the very bottom, the totally undefined value \bot ("bottom"), which
approximates everything. As the computation makes progress it climbs the order, pinning down
more of its result. Meaning, in this picture, is a limit of ever-better approximations.
A partial order on a set D is a relation
\sqsubseteq that is
- reflexive: x \sqsubseteq x;
- antisymmetric: x \sqsubseteq y \wedge y \sqsubseteq x \Rightarrow x = y;
- transitive: x \sqsubseteq y \wedge y \sqsubseteq z \Rightarrow x \sqsubseteq z.
The pair (D, \sqsubseteq) is a poset. "Partial" means some
pairs may be incomparable: neither x \sqsubseteq y nor
y \sqsubseteq x need hold.
A poset is drawn as a Hasse diagram: put smaller elements lower, larger elements
higher, and draw a line from x up to y exactly
when y covers x — that is,
x \sqsubset y with nothing strictly between. The full order is then recovered
by reading upward paths.
A lattice you can see
The cleanest first example is the powerset of a two-element set,
\mathcal{P}(\{a,b\}), ordered by inclusion
\subseteq. Its four elements form a diamond: the empty set
at the bottom, the whole set at the top, and the two singletons incomparable in the middle. Reveal it,
then read off the structure.
This diamond is a lattice: any two elements have a least upper bound (their
join x \sqcup y — here set union) and a greatest lower
bound (their meet x \sqcap y — here intersection).
The bottom \bot = \varnothing approximates everything; the top
\top = \{a,b\} is approximated by everything. The two singletons
\{a\} and \{b\} are incomparable — a
vivid reminder that the order is partial, not total.
Least upper bounds and chains
The engine of denotational semantics is not the whole lattice but one special shape inside it: an
ascending chain. Let us fix the vocabulary precisely.
Let (D, \sqsubseteq) be a poset and S \subseteq D.
-
u is an upper bound of S if
x \sqsubseteq u for every x \in S.
-
The least upper bound (lub, supremum, join)
\bigsqcup S is an upper bound that approximates every other upper bound.
When it exists it is unique (by antisymmetry).
-
A chain is a subset that is totally ordered:
d_0 \sqsubseteq d_1 \sqsubseteq d_2 \sqsubseteq \cdots. It is the
order-theoretic picture of "a computation improving its answer step by step".
Why insist only on lubs of chains, rather than of arbitrary subsets? Because a running program
produces exactly a chain of approximations, never an arbitrary set, so chains are all we ever need — and
demanding less of D lets far more sets qualify as domains.
Complete partial orders
A complete partial order (CPO, more precisely an ω-CPO) is a poset
(D, \sqsubseteq) in which
- every ascending chain d_0 \sqsubseteq d_1 \sqsubseteq \cdots has a
least upper bound \bigsqcup_{n} d_n \in D; and
- (for a pointed CPO, the kind we need) there is a least element
\bot with \bot \sqsubseteq x for all
x.
The workhorse domain is the flat domain. Take any set
X — say the naturals \mathbb{N} — add a fresh
bottom, and order it so that \bot \sqsubseteq x for every
x, while distinct "real" values stay incomparable:
\mathbb{N}_\bot \;=\; \mathbb{N} \cup \{\bot\}, \qquad x \sqsubseteq y \iff x = \bot \ \text{or}\ x = y.
Its Hasse diagram is a fan: \bot below, every number one step above it and
none above another. A flat domain models a value that is either undefined
(\bot, the loop that never returns) or fully known — with no
partial in-between. Every chain in it is eventually constant, so its lubs are trivial, yet it is exactly
the codomain we want for the meaning of an integer expression that might diverge.
Monotone and continuous functions
Domains would be inert without the right maps between them. Two conditions matter, and denotational
semantics lives or dies on the second.
Let D, E be CPOs and f : D \to E.
-
f is monotone if
x \sqsubseteq y \Rightarrow f(x) \sqsubseteq f(y): better input, no-worse
output. It never throws away information.
-
f is (Scott-)continuous if it is monotone and preserves
lubs of chains:
f\!\left(\bigsqcup_{n} d_n\right) \;=\; \bigsqcup_{n} f(d_n)
for every ascending chain (d_n).
Continuity says f can be computed by approximation: to know
f at a limit, it suffices to know f at every finite
stage and take the limit of the answers. That is precisely what a computer can do — it only ever sees
finite approximations — so continuity is the mathematical shadow of computability. Monotonicity alone is
not enough; the surprise is that on the CPOs we use, essentially every function a program can define
turns out to be continuous.
Below, a small executable check. We model the flat truth-value domain
\mathbb{B}_\bot = \{\bot, \mathsf{tt}, \mathsf{ff}\} as
0, 1, 2 (with 0 = ⊥), define \sqsubseteq, and test
a candidate function for monotonicity by brute force over all pairs.
// Flat domain B⊥ = { ⊥, tt, ff } encoded as 0, 1, 2 with 0 = ⊥.
const BOT = 0, TT = 1, FF = 2;
const carrier = [BOT, TT, FF];
// x ⊑ y iff x = ⊥ or x = y (the flat order).
function leq(x: number, y: number): boolean {
return x === BOT || x === y;
}
// A monotone "definedness-preserving not": ⊥ ↦ ⊥, tt ↦ ff, ff ↦ tt.
function strictNot(x: number): number {
if (x === BOT) return BOT;
return x === TT ? FF : TT;
}
// A BROKEN function that invents information: ⊥ ↦ tt. Should fail monotonicity.
function eagerNot(x: number): number {
if (x === BOT) return TT; // pretends to know an answer with no input!
return x === TT ? FF : TT;
}
function isMonotone(f: (n: number) => number): boolean {
for (const x of carrier)
for (const y of carrier)
if (leq(x, y) && !leq(f(x), f(y))) {
console.log(` violation: ${x} ⊑ ${y} but f(${x})=${f(x)} ⋢ f(${y})=${f(y)}`);
return false;
}
return true;
}
console.log("strictNot monotone? " + isMonotone(strictNot));
console.log("eagerNot monotone? " + isMonotone(eagerNot));
The eager version claims a definite answer from \bot, so when a
more-defined input arrives it must sometimes contradict itself — and the monotonicity check
catches it. Only functions that respect "more input, no-worse output" earn a place in the semantics.
In the late 1960s Christopher Strachey at Oxford had a philosophy of programming-language meaning but no
mathematics to anchor it — in particular, the untyped λ-calculus seemed to demand a set
D isomorphic to its own function space
D \cong (D \to D), which Cantor's theorem forbids for ordinary sets. In 1969
Dana Scott, initially sceptical that any such semantics could be rigorous, found the escape:
restrict to continuous functions. The continuous function space
[D \to E] is itself a CPO (ordered pointwise), and it is small enough
that the reflexive equation D_\infty \cong [D_\infty \to D_\infty] has a
genuine solution, built as the limit of a chain of approximating domains. Scott and Strachey turned a
philosophy into the Scott–Strachey approach, and domains have anchored programming-language
theory ever since. The moral: order did not just tame recursion — it tamed self-reference.
Two traps. First, completeness is about chains, not pairs: a lattice guarantees lubs of
finite sets, but a CPO must supply lubs of infinite ascending chains — and a poset can have all
binary joins yet miss a chain's limit (the rationals in [0,1] ordered by
\le have binary lubs but the chain approaching
1/\sqrt2 has no rational lub). Second, monotone is weaker than
continuous. Every continuous function is monotone, but not conversely: on an infinite domain a
monotone map can "jump" at a limit, disagreeing with the lub of its values along the chain. The
fixed-point theory of the next page needs the continuous ones — reach for monotonicity when you
mean continuity and your proofs will quietly break.