chosen to be a finite set of symbols that serve as labels for the components of the tuple so as to enhance readability.
The statics of finite products is given by the following rules:
Γ`e1:τ1 . . . Γ` en: τn Γ` hi1,→e1, . . . ,in,→eni: hi1,→τ1, . . . ,in,→τni (11.3a) Γ`e :hi1,→τ1, . . . ,in,→τni (1≤ k≤n) Γ `e·ik :τk (11.3b)
In Rule (11.3b) the index i ∈ I is a particularelement of the index set I, whereas in Rule (11.3a), the indexiranges over the index setI.
The dynamics of finite products is given by the following rules:
[e1val . . . en val] hi1,→e1, . . . ,in,→enival (11.4a) " e1val . . . ej−1val ej 7→e0j e0j+1=ej+1 . . . e0n=en hi1,→e1, . . . ,in,→eni 7→ hi1,→e01, . . . ,in,→e0ni # (11.4b) e 7→e0 e·i7→e0·i (11.4c) hi1,→e1, . . . ,in,→enival hi1,→e1, . . . ,in,→eni ·ik 7→ ek (11.4d)
As formulated, Rule (11.4b) specifies that the components of a tuple are to be evaluated insome sequential order, without specifying the order in which the components are considered. It is not hard, but a bit technically complicated, to impose an evaluation order by imposing a total ordering on the index set and evaluating components according to this ordering.
Theorem 11.2(Safety). If e:τ, then either evalor there exists e0such that e0 :τ and e7→e0.
Proof. The safety theorem may be decomposed into progress and preserva- tion lemmas, which are proved as in Section11.1.
11.3
Primitive and Mutual Recursion
In the presence of products we may simplify the primitive recursion con- struct defined in Chapter9so that only the result on the predecessor, and not the predecessor itself, is passed to the successor branch. Writing this as
11.3 Primitive and Mutual Recursion 99
iter(e;e0;x.e1), we may define primitive recursion in the sense of Chap-
ter9to be the expressione0·r, wheree0 is the expression iter(e;hz,e0i;x.hs(x·l),[x·r/x]e1i).
The idea is to compute inductively both the number,n, and the result of the recursive call on n, from which we can compute bothn+1 and the result of an additional recursion usinge1. The base case is computed directly as
the pair of zero ande0. It is easy to check that the statics and dynamics of
the recursor are preserved by this definition.
We may also use product types to implementmutual recursion, which allows several mutually recursive computations to be defined simultane- ously. For example, consider the following recursion equations defining two mathematical functions on the natural numbers:
E(0) =1 O(0) =0 E(n+1) =O(n) O(n+1) =E(n)
Intuitively, E(n)is non-zero if and only ifnis even, andO(n)is non-zero if and only if nis odd. If we wish to define these functions inL{nat*}, we immediately face the problem of how to define two functions simul- taneously. There is a trick available in this special case that takes advan- tage of the fact thatEandOhave the same type: simply defineeoof type nat→ nat→natso thateo(0)representsEandeo(1)representsO. (We leave the details as an exercise for the reader.)
A more general solution is to recognize that the definition of two mutu- ally recursive functions may be thought of as the recursive definition of a pair of functions. In the case of the even and odd functions we will define the labeled tuple,eEO, of type,τEO, given by
heven,→nat→nat,odd,→nat→nati.
From this we will obtain the required mutually recursive functions as the projectionseEO·evenandeEO·odd.
To effect the mutual recursion the expressioneEOis defined to be
fix this:τEOisheven,→eE,odd,→eOi, whereeE is the expression
λ(x:nat) ifz x{z⇒s(z) | s(y)⇒this·odd(y)},
100 11.4 Notes
andeOis the expression
λ(x:nat) ifz x{z⇒z | s(y)⇒this·even(y)}.
The functions eE and eO refer to each other by projecting the appropriate component from the variablethisstanding for the object itself. The choice of variable name with which to effect the self-reference is, of course, imma- terial, but it is common to usethisorselfto emphasize its role.
11.4
Notes
Product types are the essence of structured data. All languages have some form of product type, but frequently in a form that is combined with other, separable, concepts. Common manifestations of products include: (1) func- tions with “multiple arguments” or “multple results”; (2) “objects” repre- sented as tuples of mutually recursive functions; (3) “structures,” which are tuples with mutable components. There are many papers on finite prod- uct types, which include record types as a special case. Pierce(2002) pro- vides a thorough account of record types, and their subtyping properties (for which, see Chapter23). Allen et al. (2006) analyzes many of the key ideas in the framework of dependent type theory.
Chapter 12
Sum Types
Most data structures involve alternatives such as the distinction between a leaf and an interior node in a tree, or a choice in the outermost form of a piece of abstract syntax. Importantly, the choice determines the structure of the value. For example, nodes have children, but leaves do not, and so forth. These concepts are expressed by sum types, specifically the binary sum, which offers a choice of two things, and thenullary sum, which offers a choice of no things. Finite sums generalize nullary and binary sums to permit an arbitrary number of cases indexed by a finite index set. As with products, sums come in both eager and lazy variants, differing in how val- ues of sum type are defined.
12.1
Nullary and Binary Sums
The abstract syntax of sums is given by the following grammar:
Typ τ ::= void void nullary sum
sum(τ1;τ2) τ1+τ2 binary sum
Exp e ::= abort[τ](e) abort(e) abort
in[τ1;τ2][l](e) l·e left injection
in[τ1;τ2][r](e) r·e right injection
case(e;x1.e1;x2.e2) casee{l·x1⇒e1| r·x2⇒e2} case analysis
The nullary sum represents a choice of zero alternatives, and hence admits no introductory form. The eliminatory form, abort(e), aborts the com- putation in the event thate evaluates to a value, which it cannot do. The elements of the binary sum type are labeled to indicate whether they are
102 12.1 Nullary and Binary Sums
drawn from the left or the right summand, eitherl·e orr·e. A value of the sum type is eliminated by case analysis.
The statics of sum types is given by the following rules.
Γ`e:void Γ`abort(e):τ (12.1a) Γ`e:τ1 Γ `l·e:τ1+τ2 (12.1b) Γ`e:τ2 Γ `r·e:τ1+τ2 (12.1c) Γ` e:τ1+τ2 Γ,x1 :τ1` e1 :τ Γ,x2:τ2 `e2:τ Γ`casee{l·x1⇒e1| r·x2⇒e2}:τ (12.1d) For the sake of readability, in Rules (12.1b) and (12.1c) we have writtenl·e andr·ein place of the abstract syntaxin[τ1;τ2][l](e)andin[τ1;τ2][r](e),
which includes the typesτ1andτ2explicitly. In Rule (12.1d) both branches
of the case analysis must have the same type. Because a type expresses a static “prediction” on the form of the value of an expression, and because an expression of sum type could evaluate to either form at run-time, we must insist that both branches yield the same type.
The dynamics of sums is given by the following rules:
e 7→e0
abort(e)7→abort(e0) (12.2a) [e val] l·e val (12.2b) [e val] r·e val (12.2c) e7→ e0 l·e7→ l·e0 (12.2d) e7→ e0 r·e7→ r·e0 (12.2e) e 7→e0 casee{l·x1⇒e1| r·x2⇒e2}7→casee0{l·x1⇒e1| r·x2⇒e2} (12.2f) [e val] case l·e{l·x1⇒e1| r·x2⇒e2}7→[e/x1]e1 (12.2g)