• No results found

such that d 7→d0.

Proof. By induction on the structure of d. For example, if d = succ(d0), then we have by induction either d0 val, d0 err, or d0 7→ d00 for some d00. In last case we have by Rule (18.4b) that succ(d0) 7→ succ(d00), and in the second-to-last case we have by Rule (18.4c) thatsucc(d0) err. Isd0 val, then by Lemma18.1, eitherd0 is num n ord0 isnt num. In the former case succ(d0)7→ num(n+1), and in the lattersucc(d0)err. The other cases are handled similarly.

Lemma 18.3 (Exclusivity). For any d inL{dyn}, exactly one of the following holds: dval, or d err, or d7→ d0 for some d0.

Proof. By induction on the structure ofd, making reference to Rules (18.4).

18.2

Variations and Extensions

The dynamic languageL{dyn}defined in Section18.1closely parallels the static language L{nat*}defined in Chapter10. One discrepancy, how- ever, is in the treatment of natural numbers. Whereas in L{nat*} the zero and successor operations are introductory forms for the type nat, in L{dyn}they are elimination forms that act on separately-defined numer- als. This is done to ensure that there is a single class of numbers, rather than a separate class for zero and successor.

An alternative is to treatzero andsucc(d)as values of two separate classes, and to introduce the obvious class checking judgments for them. This complicates the error checking rules, and admits problematic values such assucc(λ(x)d), but it allows us to avoid having a class of numbers. When written in this style, the dynamics of the conditional branch is given as follows:

d7→ d0

ifz(d;d0;x.d1)7→ ifz(d0;d0;x.d1) (18.5a)

dis zero

ifz(d;d0;x.d1)7→d0 (18.5b)

dis succ d0

ifz(d;d0;x.d1)7→ [d0/x]d1 (18.5c)

disnt zero disnt succ

ifz(d;d0;x.d1)err (18.5d)

164 18.2 Variations and Extensions

Notice that the predecessor of a value of the successor class need not be a number, whereas in the previous formulation this possibility does not arise. Structured data may be added toL{dyn}using similar techniques. The classic example is to introduce a null value, and a constructor for combin- ing two values into one.

Exp d ::= nil nil null cons(d1;d2) cons(d1;d2) pair

ifnil(d;d0;x,y.d1) ifnild{nil⇒d0| cons(x;y)⇒d1}

conditional

The expression ifnil(d;d0;x,y.d1) distinguishes the null value from a

pair, and signals an error on any other class of value.

Lists may be represented using null and pairing. For example, the list consisting of three zeroes is represented by the value

cons(zero;cons(zero;cons(zero;nil))). But what to make of this beast?

cons(zero;cons(zero;cons(zero;λ(x)x)))

This does not correspond to a list, because it does not end withnil.

The difficulty with encoding lists using null and pair becomes appar- ent when defining functions that operate on them. For example, here is a possible definition of the function that appends two lists:

fixaisλ(x)λ(y) ifnil(x;y;x1,x2.cons(x1;a(x2)(y)))

Nothing prevents us from applying this function to any two values, re- gardless of whether they are lists. If the first argument is not a list, then execution aborts with an error. But the function does not traverse its sec- ond argument, it can be any value at all. For example, we may append a list to a function, and obtain the “list” that ends with aλgiven above.

It might be argued that the conditional branch that distinguishes null from a pair is inappropriate in L{dyn}, because there are more than just these two classes in the language. One approach that avoids this criticism is to abandon the idea of pattern matching on the class of data entirely, replacing it by a general conditional branch that distinguishes null from all other values, and adding to the languagepredicates1that test the class of a value anddestructorsthat invert the constructors of each class.

1Predicates evaluate to the null value to indicate that a condition is false, and some non- null value to indicate that it is true.

18.2 Variations and Extensions 165

In the present case we would reformulate the extension ofL{dyn}with null and pairing as follows:

Exp d ::= cond(d;d0;d1) cond(d;d0;d1) conditional

nil?(d) nil?(d) nil test cons?(d) cons?(d) pair test car(d) car(d) first projection cdr(d) cdr(d) second projection The conditional cond(d;d0;d1)distinguishes d between niland all other

values. If dis notnil, the conditional evaluates tod0, and otherwise eval-

uates to d1. In other words the value nil represents boolean falsehood,

and all other values represent boolean truth. The predicatesnil?(d)and cons?(d)test the class of their argument, yielding nilif the argument is not of the specified class, and yielding some non-nilif so. The destructors car(d)andcdr(d)2decomposecons(d1;d2)intod1andd2, respectively.

Written in this form, the append function is given by the expression

fixaisλ(x)λ(y) cond(x;cons(car(x);a(cdr(x))(y));y). The behavior of this formulation of append is no different from the earlier one; the only difference is that instead of dispatching on whether a value is either null or a pair, we instead allow discrimination on any predicate of the value, which includes such checks as special cases.

An alternative, which is not widely used, is to enhance, rather than re- strict, the conditional branch so that it includes cases for each possible class of value in the language. So, for example, in a language with numbers, functions, null, and pairing, the conditional would have four branches. The fourth branch, for pairing, would deconstruct the pair into its constituent parts. The difficulty with this approach is that in realistic languages there are many classes of data, and such a conditional would be rather unwieldy. Moreover, even once we have dispatched on the class of a value, it is never- theless necessary for the primitive operations associated with that class to perform run-time checks. For example, we may determine that a value,d, is of the numeric class, but there is no way to propagate this information into the branch of the conditional that then addsdto some other number. The addition operation must still check the class of d, recover the underlying number, and create a new value of numeric class. This is an inherent lim- itation of dynamic languages, which do not permit handling values other than classified values.

2This terminology for the projections is archaic, but firmly established in the literature.