Domain Theory


Contents


Motivation

Our motivation for studying domain theory is to allow us to define the meanings of constructs like loops, whose "natural" definitions are recursive. For example, the "natural" way to define the meaning of a while loop, i.e., to define the "command" valuation function for this case, is:

But this is a recursive definition (because C[[while B do C]] appears on both the left- and right-hand sides). To address this, we abstract on the function that is used recursively:

and take the fixed point:

But how do we know that Fix(F) exists? is unique? corresponds to our intuition?

Before answering those questions, let's consider a second example: a recursive function definition.

Again, we can use abstraction and Fix to define g:

In this case, we know that g has many fixed points:
  1. the function that maps all inputs to zero
  2. the functions that map all non-positive inputs to zero, and all positive inputs to some constant k
  3. the function that maps all non-positive inputs to zero, and all positive inputs to bottom.
Our intuition is that the third fixed point should be the meaning of g, and here's an AMAZING FACT (with some italicized terms that we'll need to define):

To understand domain and pointed cpo we first need to understand:

  1. partially ordered set (poset)
  2. complete partial order (cpo)
  3. pointed complete partial order (pointed cpo)

We'll find out that every domain is a cpo, and some are pointed. Once we understand all that, we'll go on to define continuous functions.

Partially Ordered Sets

A partially ordered set (poset) consists of:

  1. a set of elements D
  2. a partial ordering ⊆ that is
    • reflexive (∀ x in D, x ⊆ x)
    • anti-symmetric (∀ x, y in D, if ((x ⊆ y) and (y ⊆ x)) then x = y)
    • transitive (∀ x,y,z in D, if ((x ⊆ y) and (y ⊆ z)) then x ⊆ z)
    Note that the ordering is "partial" (not total) because in general there can be elements x, y in D that are incomparable.

Some posets have a least element ⊥ (bottom), and some posets have a greatest element "top". Some pairs of elements have a "least upper bound" (lub) or "join" (denoted here by U) and defined as follows:

Some pairs of elements have a "greatest lower bound" (glb) or "meet" (denoted here by M), defined similarly.


TEST YOURSELF #1

Complete the definition for the meet operation.

solution


Join and meet can be defined for sets as well as pairs. Given X a subset of D, U(X) = z (i.e., the join over all elements in set X is z) iff both of the following hold:

  1. ∀ x in X, x⊆z, and
  2. ∀ w in D such that ∀ x in X, x ⊆ w: z ⊆ w

Note that z must be in D, but not necessarily in X.

Meet over a set is defined similarly.

Digression: For the purposes of dataflow analysis, we are interested in lattices, which are posets in which every pair of elements has both a join and a meet. Furthermore, a lattice is complete iff every subset of elements has both a join and a meet (including infinite subsets if D is infinite). End digression.

Examples

Let's look at some example sets and orderings, and decide for each whether it is a partially-ordered set, a lattice, or a complete lattice. In each case, we use a picture to show the ordering; a vertical line up from an item x to an item y means that x ⊆ y; a horizontal line from x to y means that x ⊆ y and y ⊆ x.

Example 1: Some English words, ordered by "is a substring of".


                        candycane
                        /       \
                   cane       candy     there   then
                       \     /    \      /  \   /  \
                         can     and   here  the    hen
                            \   /         \   |    /
                              an           \  |   /
                                             he
This ordering is reflexive, anti-symmetric, and transitive, so this is a poset. However, not all pairs have a meet and join (e.g., and, here) so this is not a lattice.

Example 2: Some English words ordered by length

                                        candy
                                       /  |   \
                                      /   |    \
                                    can - the - hen
                                      \   |    /
                                       \  |   /
                                          an
In this case, the ordering is reflexive, but not anti-symmetric (e.g., canthecan, but can != the) so this is not a poset (so it is certainly not a lattice).

Example 3: Integers ordered by the usual "less than or equal to" ordering.

                .
                .
                .
                2
                |
                1
                |
                0
                |
               -1
                .
                .
                .
This ordering is reflexive, anti-symmetric, and transitive, so this is a poset. All pairs do have a meet (min) and a join (max), so this is also a lattice. However, not all subsets have a meet and a join (all finite subsets do have a meet and a join, but some infinite subsets do not; e.g., the set of all positive integers has no join) so this is not a complete lattice.

Example 4: The set of integers plus special top and bottom elements, ordered as follows:

                  top
                / / \ \
         ...  -2 -1 0  1  ...
               \ \  / /
                bottom
This is called a flat ordering, and this example is a poset, a lattice, and a complete lattice.

Complete Partial Order (cpo)

Given a partially-ordered set (D, ⊆), a subset X of D is a chain iff ∀ a, b in X, either (a ⊆ b) or (b ⊆ a).

Note that a chain can be finite or it can be infinite. If a chain is finite, then it has both a least-upper and a greatest-lower bound (in D); however, this is not necessarily true if the chain is infinite. For example, let D be the set of integers with their normal ordering. The positive numbers form a chain that has no limit (no least upper bound, no join) in D.

Definition: A partially-ordered set (D, ⊆) is a complete partial order (cpo) iff every chain in D has a least upper bound (in D).

Pointed CPO

Definition: A partially-ordered set (D, ⊆) is a pointed cpo iff it is a cpo and it has a least element (⊥).

Let's take a minute to consider the relationship between complete lattices, complete partial orders, and pointed cpos. Recall that for a complete lattice, every subset X of D has both a meet (a greatest lower bound) and a join (a least upper bound), while for a cpo, every chain has a least upper bound

This means that every complete lattice is a cpo, but not vice versa: since in a lattice every subset has a least upper bound, and every chain is a subset, it must be that every chain has a least upper bound; however, in a cpo there is no guarantee that even pairs of elements, much less subsets, have a greatest lower bound, and there is no guarantee that non-chain subsets have a least upper bound.

Similarly, every complete lattice is a pointed cpo, but not vice versa. In a lattice every subset has a greatest lower bound; since D itself is a subset of D, that means there is a greatest lower bound for the whole set; i.e., it has a bottom element (which is what is required for a pointed cpo beyond what is required for a cpo). However, pointed cpos don't guarantee pairwise-meet or pairwise-join, so they are not necessarily lattices, much less complete ones.

Domain

Recall that we started looking at partial orders because we wanted to define domain (because that's what showed up in the "amazing fact" about fixed points). We're finally ready to define domains. Every domain is a cpo. Below are some (common) cpo constructors (i.e., ways to build new domains out of simpler ones).

  1. Primitive Domains: Any set of elements with the "flat" ordering (and no top or bottom element) is a primitive domain. Recall that in a flat ordering: (x ⊆ y) iff (x = y).

    These are clearly cpos (every chain is of length 1), but are not pointed.

  2. Lifted Domains: Given domain D = (A, ⊆), its lifting, D is (A union {⊥}, ⊆D), where ⊆D is defined to be: (x ⊆D y) iff (x = ⊥) or (x ⊆ y).

    These are pointed cpos.

    Here are some examples of lifted domains:

    • B = ({true, false}, flat ordering)
      B = ({true, false, ⊥}, true, false incomparable to each other, both ⊃ ⊥)

    • N = ({0,1,2,...}, flat ordering)
      N = ({⊥,0,1,2,...}, all numbers incomparable to each other, all ⊃ ⊥)

  3. Cross Product Domains: Given domains D1 = (A, ⊆A) and D2 = (B, ⊆B), their cross-product D1 x D2 is
      ({(a,b) | (a in A) and (b in B)}, ⊆D1xD2)

    where ⊆D1xD2 is defined to be:

      (a1, b1) ⊆D1xD2 (a2, b2) iff (a1A a2) and (b1B b2)

    Note that the elements of D1 x D2 are pairs, with the first element from D1 and the second from D2.

    CLAIM 1: if D1 and D2 are cpos then so is D1 x D2.

    PROOF: We must show that ⊆D1xD2 is reflexive, anti-symmetric, and transitive, and that all chains have least upper bounds. The first part is left as an exercise; the second is done below. In the proof, the symbol ⊆ is used without subscripts; sometimes it means ⊆A, sometimes it means ⊆B, and sometimes it means ⊆D1xD2; however, which it is should be clear from the context.

    Chains in D1 x D2 are of the form:

      ... ⊆ (ak, bk) ⊆ (ak+1, bk+1) ⊆ ...

    such that, ∀ j: aj ⊆ aj+1 and bj ⊆ bj+1. Since D1 and D2 are cpos, there exist (x in A) and (y in B) such that: x = lub of the ajs, and y = lub of the bjs. The element (x, y) of D1 x D2 is certainly an upper bound for the (aj, bj) chain; we must now show that is it the least upper bound. We will do that by contradiction; i.e., we will assume that it is not the least upper bound and show that leads to a contradiction.

    If (x, y) is not the least upper bound for the (aj, bj) chain, then there is some (v, w) such that:

    1. ∀ j, (aj, bj) ⊆ (v, w), and
    2. (v, w) ⊆ (x, y)

    But then ∀ j, (ajA v) and v ⊆A x, so x is not the lub of the ajs, which contradicts our assumption.


    TEST YOURSELF #2

    Complete the proof that D1 x D2 is a cpo by showing that ⊆D1xD2 is reflexive, anti-symmetric, and transitive.

    solution


    CLAIM 2: if D1 and D2 are pointed cpos (with least elements ⊥A and ⊥B) then D1 x D2 is also pointed, with least element (⊥A, ⊥B).

    Note: It is not necessary to "lift" D1 x D2 to get a pointed cpo. Lifting D1 x D2 would produce a pointed cpo, but it would be a different one: the least element of D1 x D2 is the pair (⊥A, ⊥B), while the least element of Lift(D1 x D2) is a new element, ⊥, which is ⊂ (⊥A, ⊥B).

  4. Disjoint Union Domains: Given D1 = (A, ⊆A) and D2 = (B, ⊆B), their disjoint union D1 + D2 is:

    where the numbers 0 and 1 are "tags" that tell where the element came from (so if there is some x in both A and B, there will be two elements in D1+D2: (0,x) and (1,x)).

    The ordering ⊆A+B is defined as follows:

    CLAIM: if D1 and D2 are cpos then so is D1+D2. The proof is left to you!

    Note that D1+D2 is not pointed even if both D1 and D2 are (because ⊥A is not less than any item (1, *), and ⊥B is not less then any item (0, *)).

  5. Function Space Domains: Given domains D1 = (A, ⊆A) and D2 = (B, ⊆B), their function space D1→D2 is:

    (we will define continuous functions soon!) where ⊆A→B is defined as follows:

    Caution: Remember that all domains are built starting with primitive domains, which have flat orderings. So f1A→B f2 really comes down to "f1 is less defined than f2". If you think of a function as a set of ordered pairs (with no pair for an element x that is mapped to bottom), then f1A→B f2 iff f1's set of pairs is a subset of f2's set of pairs.

    Here are some examples to illustrate ordering of functions.

    1. f1, f2: int → int

      f1(x) = x - 1
      f2(x) = x + 1

      It is not true that f1 ⊆ f2; they are both defined for the same set of inputs (all integers). The fact that f1(x) is less than f2(x) using the usual "less than" for integers is irrelevant; the ordering that is relevant is the flat ordering, according to which no two integers are comparable. So f1(x) and f2(x) are not comparable, and neither are f1 and f2 themselves.

    2. f1, f2: int → int

      f1(x) = if x <= 0 then ⊥ else x
      f2(x) = if x == 0 then ⊥ else x

      Comparing f1 and f2 element-wise we have:
      f1: ... (-1, ⊥) (0, ⊥) (1, 1) (2, 2) ...
      f2: ... (-1, -1) (0, ⊥) (1, 1) (2, 2) ...

      In this example, it is true that f1 ⊂ f2, because everywhere that f1 produces a value other than ⊥ it produces the same value as f2, and there are some places where f1 produces ⊥ while f2 produces non-⊥.

    3. f1, f2: int → int

      f1(x) = if x = 0 then ⊥ else x
      f2(x) = if x = 0 then ⊥ else x+1

      In this example, it is not true that f1 ⊆ f2, because different, non-⊥ values are not comparable.

Continuous Functions

We need to define continuous functions because:

Continuous functions are a special case of monotonic functions, which are defined as follows:

Caution again: Remember the orderings that domains can have. If A and B are posets that are domains, then the orderings have to do with "more defined". For example, consider function f of type:

where Z is the lifted int domain (integers union ⊥). Function f sums its first two arguments (where the sum is ⊥ if either argument is ⊥), and pairs that value with the value of its third argument. For example:

Function f is monotonic; the values used in the example are ordered like this:
                           x                        f(x)

                         <1,2,3>                    <3,3>                  
                       /         \                 /     \
                <1,2,⊥>         <1, ⊥,3>     <3, ⊥>      <⊥, 3>  
                                    |              \     /              
                                <⊥, ⊥, 3>          <⊥, ⊥>         


TEST YOURSELF #3

Question 1: Assume that function f is of type D→D, where D is the flat domain of integers with ⊥. If I tell you that f(2) = 3, and f(3) = ⊥, can you say anything about the monotonicity of f?

Question 2: Now assume that function f is of type (D x D) → D, where D is as above. If I tell you that f(<2,⊥>) = 3, and f(<2,3>) = ⊥, can you say anything about the monotonicity of f?

solution


Now we're ready to define continuous functions:

Now that we know what a continuous function is, let's reconsider the "function space" domain constructor:

CLAIM: if D1 and D2 (with underlying sets A and B) are cpos then so is D1 → D2.

PROOF: We must show that all chains in D1→D2 have limits that are in D1 → D2.

A chain in D1 → D2 is of the form: ... ⊆ fk ⊆ fk+1 ⊆ fk+2 ⊆ ... Consider a single element a in A (the underlying set of D1). Then:

must be a chain, too (using D2's ordering). This follows from the definition of the ⊆ ordering of D1 → D2. Since D2 is a cpo, this chain must have a limit in B. Similar chains (with limits) must exist for all a in A.

Now, to finish the proof we will show that function g, defined below, is the limit of the chain ... ⊆ fk ⊆ fk+1 ⊆ fk+2 ⊆ ..., and is in D1 → D2.

To show that g is the limit of the chain and is in D1 → D2, we will show:

  1. g is an upper bound for the chain
  2. g is the least upper bound for the chain
  3. g is in D1 → D2

(1) follows immediately from the definition of g (and of "upper bound"). (2) can be proved by contradiction (similar to the proof used for (D1 x D2)). To show (3), note that the type of g is: A → B. To show that it is in D1 → D2, we must just show that it is continuous (since D1 → D2 includes all continuous functions of that type); so we must show that given any chain ... ⊆ ak ⊆ ak+1 ⊆ ... in A:

Here is the proof:
LHS = g(lub{..., ak, ak+1, ...})
= lub{..., fj(lub{...ak, ak+1, ...}), fj+1(lub{...ak, ak+1, ...}), ...}, by def of g

RHS = lub{...g(ak, g(ak+1), ...}
= lub{lub{..., fj(ak), fj(ak+1), ...}, ...} by def of g

= lub{lub{fj(ak), fj(ak+1), ...}, lub{..., fj+1(ak), fj+1(ak+1), ...}, ...} because lub is associative and commutative

= lub{..., fj(lub{..., ak, ak+1, ...}), fj+1(lub{..., ak, ak+1, ...}), ...} because all of the f's are continuous

CLAIM: if D1 and D2 are both pointed cpos, then so is D1 → D2

PROOF: The function λa. ⊥B (where "⊥B" is the ⊥ element from D2) is of type A → B, and is continuous, and is ⊆ all other functions in D1 → D2 (since ⊥B is ⊆ all other elements in B). Therefore, that function is in D1 → D2, and is its least element.

The Main Result!

THEOREM: If domain D is a pointed cpo, and F is a continuous function of type D → D, then F has a least fixed point, defined as follows:

where:

PROOF: We must show two things:

  1. First we will show that fix(F), defined above, is a fixed point of F (i.e., we will show that F(fix(F)) = fix(F)).

    To do this, we start by showing that F0(⊥), F1(⊥), ... is a chain. This is because F is monotonic: let x = ⊥ and y = F(⊥). Since ⊥ ⊆ everything, x is ⊆ y. Since F is monotonic, F(x) ⊆ F(y). By induction, for all i, Fi(⊥) ⊆ Fi+1(⊥).

    Now we will show that fix(F) = F(fix(F))

    LHS = fix(F)
    RHS = F(fix(F))
    = F( lub{ Fi( ⊥ ) | i >= 0} ) by def of fix(F)
    = lub{ F( Fi( ⊥ ) ) | i >=0 } since F is continuous and the Fi's form a chain
    = lub{ Fi( ⊥ ) | i >=1 } by def of Fi
    = lub{ Fi( ⊥ ) | i >=0 } ***
    = fix(F) by def of fix(F)

    To justify the step marked ***:

    • F0( ⊥ ) is defined to be ⊥
    • since ⊥ is ⊆ everything, it is ⊆ all items in the set { Fi( ⊥ ) | i >= 1 }
    • adding an item that is ⊆ everything else to the set doesn't change the lub of the set

  2. Now we must show that fix(F) is the least fixed point of F.

    By definition of lub:

    1. forall x in { Fi( ⊥ ) | i >= 0 }, x ⊆ fix(F)
    2. forall y such that forall x in { Fi( ⊥ ) | i >= 0}: x ⊆ y, and fix(F) ⊆ y

    (a) says that fix(F) is an upper bound; (b) says that it is the least upper bound.

    Consider e, an arbitrary other fixed point of F; we must show that fix(F) ⊆ e:

      F0(⊥) = ⊥, which is ⊆ e
      since F is monotonic:
        F1(⊥) ⊆ F1(e) = e (since e is a fixed point of F)
        F2(⊥) ⊆ F2(e) = e
        ...

      So forall i >= 0, Fi(⊥) ⊆ e

    This means that e is also an upper bound for the chain; but by definition, fix(F) is the least upper bound, so it must be that fix(F) ⊆ e.

END PROOF

Note: If D has no infinite ascending chains, then you can actually compute fix(F) by "iterating up from ⊥" (this is done, e.g., by iterative dataflow analysis algorithms). If D does have infinite chains, you can't always compute fix(F), but you can keep getting better and better approximations. We can illustrate this using the factorial function. In the illustration below, ⊥ means the bottom function; i.e., the function λn.⊥ that maps all values to bottom.

First we define factorial as a recursive function, abstract, and take the fixed point:

Now we consider the approximations that we get by iterating up from bottom:

Note that:

Also note that this (least) fixed point does correspond to our intuition, which says that factorial should be undefined (bottom) for negative numbers.

Finally, recall that we want to use this "fixed-point theorem" to justify defining meaning functions for circular and recursive constructs in programming languages. So we need to know that those functions involve domains, pointed cpos, and continuous functions over them. So we should make sure that all the valuation functions we define have the right properties. If you're interested, you can borrow the book on denotational semantics by David Schmidt, which gives a somewhat informal argument that this is indeed the case.

And on that note, we finish our study of domain theory!