8

Chapter 2

The Mini-ML Language

Unfortunately one often pays a price for [languages which impose no discipline of types] in the time taken to find rather inscrutable bugs—anyone who mistakenly applies CDR to an atom in LISP and finds himself absurdly adding a property list to an integer, will know the symptoms.

— Robin Milner

A Theory of Type Polymorphism in Programming [Mil78]

In preparation for the formalization of Mini-ML in a logical framework, we begin with a description of the language in a common mathematical style. The version of Mini-ML we present here lies in between the language introduced in [CDDK86] and call-by-value PCF [Plo75, Plo77]. The description consists of three parts: (1) the abstract syntax, (2) the operational semantics, and (3) the type system. Logically, the type system would come before the operational semantics, but we postpone the more difficult typing rules until Section 2.5.

2.1 Abstract Syntax

The language of types centrally affects the kinds of expression constructs that should be available in the language. The types we include in our formulation of Mini-ML are natural numbers, products, and function types. Many phenomena in the theory of Mini-ML can be explored with these types; some others are the subject of Exercises 2.6, 2.7, and 2.9. For our purposes it is convenient to ignore certain questions of concrete syntax and parsing and present the abstract syntax of the language in Backus Naur Form (BNF). The vertical bar “|” separates alternatives on the right-hand side of the definition symbol “::=”. Definitions in this style can be understood as inductive definitions of syntactic categories such as types or expressions.

Types τ ::= nat | τ_{1} × τ_{2} | τ_{1} → τ_{2} | α

Here, nat stands for the type of natural numbers, τ_{1} ×τ_{2} is the type of pairs with elements from τ_{1} and τ_{2}, τ_{1} → τ_{2} is the type of functions mapping elements of type τ_{1} elements of type τ_{2}. Type variables are denoted by α. Even though our language supports a form of polymorphism, we do not explicitly include a polymorphic type constructor in the language; see Section 2.5 for further discussion of this issue. We follow the convention that × and → associate to the right, and that × has higher precendence than →. Parentheses may be used to explicitly group type expressions. For example,

nat × nat → nat → nat

denotes the same type as

(nat × nat) → (nat → nat).

For each concrete type (excluding type variables) we have expressions that allow us to construct elements of that type and expressions that allow us to discriminate elements of that type. We choose to completely separate the languages of types and expressions so we can define the operational semantics without recourse to typing. We have in mind, however, that only well-typed programs will ever be executed.

Expressions e ::= z | s e | (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) Natural numbers
| ⟨e_{1}, e_{2}⟩ | fst e | snd e  Pairs
| lam x. e | e_{1} e_{2}  Functions
| let val x = e_{1} in e_{2}  Definitions
| let name x = e_{1} in e_{2}
| fix x. e  Recursion
| x  Variables

Most of these constructs should be familiar from functional programming languages such as ML: z stands for zero, s e stands for the successor of e. A caseexpression chooses a branch based on whether the value of the first argument is zero or non-zero. Abstraction, lam x. e, forms functional expressions. It is often written λx. e, but we will reserve “λ” for the formal meta-theory. Application of a function to an argument is denoted simply by juxtaposition.

Definitions introduced by let val provide for explicit sequencing of computation, while let name introduces a local name abbreviating an expression. The latter incorporates a form of polymorphism (see ??).

Recursion is introduced via a fixpoint construct. A fixpoint of a function f is an element e such that f e = e. If we think of fix x. e as an operator fix acting on the function λx. e, then (λx. e) (fix (λx. e)) should be equal^{1} to fix (λx. e). Of course, not every function has a fixpoint and our semantics will have to account for this.

1in a sense not made precise here.

We use e, e′, …, possibly subscripted, to range over expressions. The letters x, y, and occasionally u and v, range over variables. We use boldface for language keywords. Parentheses are used for explicit grouping as for types. Juxtaposition associates to the left. The period (in lam x. and fix x. ) and the keywords in and of act as a prefix whose scope extends as far to the right as possible while remaining consistent with the present parentheses. For example, lam x. x z stands for lam x. (x z) and

let val x = z in case x of z ⇒ y | s x′ ⇒ f x′ x

denotes the same expression as

let val x = z in (case x of z ⇒ y | s x′ ⇒ ((f x′) x)).

As a first example, consider the following implementation of the predecessor function, where the predecessor of 0 is defined to be 0.

pred = lam x. case x of z ⇒ z | s x′ ⇒ x′

Here “=” introduces a definition in our (informal) mathematical meta-language. As a second example, consider the following definition of addition.

plus_{1} = fix add. lam x. lam y. case x of z ⇒ y | s x′ ⇒ s (add x′ y)

The considerations regarding the interpretation of fixpoint expressions yield the “equation”

plus_{1} “=” lam x. lam y. case x of z ⇒ y | s x′ ⇒ s (plus_{1} x′ y)

which will be a guide in specifying the operational semantics of fix. Continuing our informal reasoning we find that for any two arguments n and m representing natural numbers we have

plus_{1} n m “=” case n of z ⇒ m | s x′ ⇒ s (plus_{1} x′ m)

and finally, by analyzing the cases for n we arrive at the familiar recursive equantions for addition:

plus_{1} z m  “=” m
plus_{1} (s n′) m “=” s (plus_{1} n′ m)

The reader may want to convince himself now or after the detailed presentation of the operational semantics that the following are correct alternative definitions of addition.

plus_{2} = lam y. fix add. lam x. case x of z ⇒ y | s x′ ⇒ s (add x′)
plus_{3} = fix add. lam x. lam y. case x of z ⇒ y | s x′ ⇒ (add x′ (s y))

2.2 Substitution

The concepts of free and bound variable are fundamental in this and many other languages. In Mini-ML variables are scoped as follows:

case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3} binds x in e_{3},
lam x. e  binds x in e,
let val x = e_{1} in e_{2}  binds x in e_{2},
let name x = e_{1} in e_{2}  binds x in e_{2},
fix x. e  binds x in e.

An occurrence of variable x in an expression e is a bound occurrence if it lies within the scope of a binder for x in e, in which case it refers to the innermost enclosing binder. Otherwise the variable is said to be free in e. For example, the two nonbinding occurrences of x and y below are bound, while the occurrence of u is free.

let name x = lam y. y in x u

The names of bound variables may be important to the programmer’s intuition, but they are irrelevant to the formal meaning of an expression. We therefore do not distinguish between expressions which differ only in the names of their bound variables. For example, lam x. x and lam y. y both denote the identity function. Of course, variables must be renamed “consistently”, that is, corresponding variable occurrences must refer to the same binder. Thus

lam x. lam y. x = lam u. lam y. u

but

lam x. lam y. x ≠ lam y. lam y. y.

When we wish to be explicit, we refer to expressions which differ only in the names of their bound variables as α-convertible and the renaming operation as α-conversion. Languages in which meaning is invariant under variable renaming are said to be lexically scoped or statically scoped, since it is clear from program text, without considering the operational semantics, where a variable occurrence is bound. Languages such as Lisp which permit dynamic scoping for some variables are semantically less transparent and more difficult to describe formally and reason about.

A fundamental operation on expressions is substitution, the replacement of a free variable by an expression. We write [e′/x]e for the result of substituting e′ for all free occurrences of x in e. During this substution operation we must make sure that no variable that is free in e′ is captured by a binder in e. But since we may tacitly rename bound variables, the result of substitution is always uniquely defined. For example,

[x/y]lam x. y = [x/y]lam x′. y = lam x′. x ≠ lam x. x.

This form of substitution is often called capture-avoiding substitution. It is the only meaningful form of substitution under the variable renaming convention: with pure textual replacement we could conclude that

lam x. x = [x/y](lam x. y) = [x/y](lam x′. y) = lam x′. x,

which is clearly nonsensical.

Substitution has a number of obvious and perhaps not so obvious properties. The first class of properties can properly be considered part of the definition of substitution. These are equalities of the form

[e′/x]x = e′
[e′/x]y = y  for x ≠ y
[e′/x](e_{1} e_{2}) = ([e′/x]e_{1}) ([e′/x]e_{2})
[e′/x](lam y. e) = lam y. [e′/x]e  for x ≠ y and y not free in e′.

Of course, there exists one of these equations for every construct in the language. A second important property states that consecutive substitutions can be permuted with each other under certain circumstances:

[e_{2}/x_{2}]([e_{1}/x_{1}]e) = [([e_{2}/x_{2}]e_{1})/x_{1}]([e_{2}/x_{2}]e)

provided x_{1} does not occur free in e_{2}. The reader is invited to explore the formal definition and properties of substitution in Exercise 2.8. We will take these simple properties largely for granted.

2.3 Operational Semantics

The first judgment to be defined is the evaluation judgment, e ↪ v (read: e evaluates to v). Here v ranges over expressions; in Section 2.4 we define the notion of a value and show that the result of evaluation is in fact a value. For now we only informally think of v as representing the value of e. The definition of the evaluation judgment is given by inference rules. Here, and in the remainder of these notes, we think of axioms as inference rules with no premisses, so that no explicit distinction between axioms and inference rules is necessary. A definition of a judgment via inference rules is inductive in nature, that is, e evaluates to v if and only if e ↪ v can be established with the given set of inference rules. We will make use of this inductive structure of deductions throughout these notes in order to prove properties of deductive systems.

This approach to the description of the operational semantics of programming languages goes back to Plotkin [Plo75, Plo81] under the name of structured operational semantics and Kahn [Kah87], who calls his approach natural semantics. Our presentation follows the style of natural semantics.

We begin with the rules concerning the natural numbers.

\frac{ev z}{z ↪ z}  \begin{aligned}e ↪ v \\ & ev s \\ s e ↪ s v \end{aligned}

The first rule expresses that z is a constant and thus evaluates to itself. The second expresses that s is a constructor, and that its argument must be evaluated, that is, the constructor is not eager and not lazy.^{2} Note that the second rule is schematic in e and v: any instance of this rule is valid.

The next two inference rules concern the evaluation of the case construct. The second of these rule requires substitution as introduced in the previous section.

e_{1} ↪ z e_{2} ↪ v

ev case z

(case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

e_{1} ↪ s v′_{1} [v′_{1}/x]e_{3} ↪ v

ev case s

(case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

The substitution of v′_{1} for x in case e_{1} evaluates to s v′_{1} eliminates the need for environments which are present in many other semantic definitions. These rules are declarative in nature, that is, we define the operational semantics by declaring rules of inference for the evaluation judgment without actually implementing an interpreter. This is exhibited clearly in the two rules for the conditional: in an interpreter, we would evaluate e_{1} and then branch to the evaluation of e_{2} or e_{3}, depending on the value of e_{1}. This interpreter structure is not contained in these rules; in fact, naive search for a deduction under these rules will behave differently (see Section 4.3).

As a simple example which can be expressed using only the four rules given so far, consider the derivation of (case s (s z) of z ⇒ z | s x′ ⇒ x′) ↪ s z. This would arise as a subdeduction in the derivation of pred (s (s z)) with the earlier definition of pred.

\frac{ev z}{z ↪ z} ; ev s ; s z ↪ s z

ev s

s (s z) ↪ s (s z)

\frac{ev z}{z ↪ z} ; ev s ; s z ↪ s z

ev case s

(case s (s z) of z ⇒ z | s x′ ⇒ x′) ↪ s z

The conclusion of the second premiss arises as [(s z)/x′]x′ = s z. We refer to a deduction of a judgment e ↪ v as an evaluation deduction or simply evaluation of e. Thus deductions play the role of traces of computation.

2For more on this distinction, see Exercise 2.12.

Pairs do not introduce any new ideas.

e_{1} ↪ v_{1} e_{2} ↪ v_{2} ev pair

⟨e_{1}, e_{2}⟩ ↪ ⟨v_{1}, v_{2}⟩

e ↪ ⟨v_{1}, v_{2}⟩ ; ev fst ; fst e ↪ v_{1}  e ↪ ⟨v_{1}, v_{2}⟩ ; ev snd ; snd e ↪ v_{2}

This form of operational semantics avoids explicit error values: for some expressions e there simply does not exist any value v such that e ↪ v would be derivable. For example, when trying to construct a v and a deduction of the expression (case ⟨z, z⟩ of z ⇒ z | s x′ ⇒ x′) ↪ v, one arrives at the following impasse:

\frac{ev z}{z ↪ z} \frac{ev z}{z ↪ z}

ev pair

⟨z, z⟩ ↪ ⟨z, z⟩ ?

?

case ⟨z, z⟩ of z ⇒ z | s x′ ⇒ x′ ↪ v

There is no inference rule “?” which would allow us to fill v with an expression and obtain a valid deduction. This particular kind of example will be excluded by the typing system, since the argument which determines the cases here is not a natural number. On the other hand, natural semantics does not preclude a formulation with explicit error elements (see Exercise 2.9).

In programming languages such as Mini-ML functional abstractions evaluate to themselves. This is true for languages with call-by-value and call-by-name semantics, and might be considered a distinguishing characteristic of evaluation compared to normalization.

\frac{ev lam}{lam x. e ↪ lam x. e}
e_{1} ↪ lam x. e′_{1}  e_{2} ↪ v_{2}  [v_{2}/x]e′_{1} ↪ v ev app
e_{1} e_{2} ↪ v

This specifies a call-by-value discipline for our language, since we evaluate e_{2} and then substitute the resulting value v_{2} for x in the function body e′_{1}. In a call-by-name discipline, we would omit the second premiss and the third premiss would be [e_{2}/x]e′_{1} ↪ v (see Exercise 2.12).

The inference rules above have an inherent inefficiency: the deduction of a judgment of the form [v_{2}/x]e′_{1} ↪ v may have many copies of a deduction of v_{2} ↪ v_{2}.

In an actual interpreter, we would like to evaluate e′_{1} in an environment where x is bound to v_{2} and simply look up the value of x when needed. Such a modification in the specification, however, is not straightforward, since it requires the introduction of closures. We make such an extension to the language as part of the compilation process in Section 6.1.

The rules for let are straightforward, given our understanding of function application. There are two variants, depending on whether the subject is evaluated (let val) or not (let name).

e_{1} ↪ v_{1} [v_{1}/x]e_{2} ↪ v

ev letv

let val x = e_{1} in e_{2} ↪ v

[e_{1}/x]e_{2} ↪ v

ev letn

let name x = e_{1} in e_{2}

The let val construct is intended for the computation of intermediate results that may be needed more than once, while the let name construct is primarily intended to give names to functions so they can be used polymorphically (see Exercise 2.22).

Finally, we come to the fixpoint construct. Following the considerations in the example on page 11, we arrive at the rule

[fix x. e/x]e ↪ v

ev fix

fix x. e ↪ v

Thus evaluation of a fixpoint construct unrolls the recursion one level and evaluates the result. Typically this uncovers a lam-abstraction which evaluates to itself. This rule clearly exhibits another situation in which an expression does not have a value: consider fix x. x. There is only one rule with a conclusion of the form fix x. e ↪ v, namely ev fix. So if fix x. x ↪ v were derivable for some v, then the premiss, namely [fix x. x/x]x ↪ v would also have to be derivable. But [fix x. x/x]x = fix x. x, and the instance of ev fix would have to have the form

fix x. x ↪ v ; ev fix. ; fix x. x ↪ v

Clearly we have made no progress, and hence there is no evaluation of fix x. x. As an example of a successful evaluation, consider the function which doubles its argument.

double = fix f. lam x. case x of z ⇒ z | s x′ ⇒ s (s (f x′))

The representation of the evaluation tree for double (s z) uses a linear notation which is more amenable to typesetting. The lines are shown in the order in which they would arise during a left-to-right, depth-first construction of the evaluation deduction. Thus it might be easiest to read this from the bottom up. We use double as a short-hand for the expression shown above and not as a definition within the language in order to keep the size of the expressions below manageable. Furthermore, we use double′ for the result of unrolling the fixpoint expression double once.

1 double′ ↪ double′  ev lam
2 double ↪ double′  ev fix 1
3 z ↪ z  ev z
4 s z ↪ s z  ev s 3
5 z ↪ z  ev z
6 s z ↪ s z  ev s 5
7 double′ ↪ double′  ev lam
8 double ↪ double′  ev fix 1
9 z ↪ z  ev z
10 z ↪ z  ev z
11 z ↪ z  ev z
12 (case z of z ⇒ z | s x′ ⇒ s (s (double x′))) ↪ z  ev case z 10, 11
13 double z ↪ z  ev app 8, 9, 12
14 s (double z) ↪ s z  ev s 13
15 s (s (double z)) ↪ s (s z)  ev s 14
16 (case s z of z ⇒ z | s x′ ⇒ s (s (double x′))) ↪ s (s z) ev case s 6, 15
17 double (s z) ↪ s (s z)  ev app 2, 4, 16

where

double = fix f. lam x. case x of z ⇒ z | s x′ ⇒ s (s (f x′))
double′ = lam x. case x of z ⇒ z | s x′ ⇒ s (s (double x′))

The inefficiencies of the rules we alluded to above can be seen clearly in this example: we need two copies of the evaluation of s z, one of which should in principle be unnecessary, since we are in a call-by-value language (see Exercise 2.11).

2.4 A First Meta-Theorem: Evaluation Returns a Value

Before we discuss the type system, we will formulate and prove a simple metatheorem. The set of values can be described by the BNF grammar

Values v ::= z | s v | ⟨v_{1}, v_{2}⟩ | lam x. e.

This kind of grammar can be understood as a form of inductive definition of a subcategory of the syntactic category of expressions: a value is either z, the successor of a value, a pair of values, or any lam-expression. There are alternative equivalent definition of values, for example as those expressions which evaluate to themselves (see Exercise 2.13). Syntactic subcategories (such as values as a subcategory of expressions) can also be defined using deductive systems. The judgment in this case is unary: e Value. It is defined by the following inference rules.

\frac{val z}{z Value}  e Value ; val s ; s e Value
e_{1} Value e_{2} Value

val pair

⟨e_{1}, e_{2}⟩ Value

\frac{val lam}{lam x. e Value}

Again, this definition is inductive: an expression e is a value if and only if e Value can be derived using these inference rules. It is common mathematical practice to use different variable names for elements of the smaller set in order to distinguish them in the presentation. But is it justified to write e ↪ v with the understanding that v is a value? This is the subject of the next theorem. The proof is instructive as it uses an induction over the structure of a deduction. This is a central technique for proving properties of deductive systems and the judgments they define. The basic idea is simple: if we would like to establish a property for all deductions of a judgment we show that the property is preserved by all inference rules, that is, we assume the property holds of the deduction of the premisses and we must show that the property holds of the deduction of the conclusion. For an axiom (an inference rule with no premisses) this just means that we have to prove the property outright, with no assumptions. An important special case of this induction principle is an inversion principle: in many cases the form of a judgment uniquely determines the last rule of inference which must have been applied, and we may conclude the existence of a deduction of the premiss.

Theorem 2.1 (Value Soundness) For any two expressions e and v, if e ↪ v is derivable, then v Value is derivable.

Proof: The proof is by induction over the structure of the deduction D :: e ↪ v. We show a number of typical cases.

Case: D = \frac{ev z.}{z ↪ z} Then v = z is a value by the rule val z.

Case:

D = \begin{matrix}D_{1} \\ e_{1} ↪ v_{1} \end{matrix} ; ev s. ; s e_{1} ↪ s v_{1}

The induction hypothesis on D_{1} yields a deduction of v_{1} Value. Using the inference rule val s we conclude that s v_{1} Value.

Case:

D = \begin{matrix}D_{1} \\ e_{1} ↪ z \end{matrix}  \begin{matrix}D_{2} \\ e_{2} ↪ v \end{matrix} ev case z. (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

Then the induction hypothesis applied to D_{2} yields a deduction of v Value, which is what we needed to show in this case.

Case:

D = \begin{matrix}D_{1} \\ e_{1} ↪ s v′_{1} \end{matrix} \begin{matrix}D_{3} \\ [v′_{1}/x]e_{3} ↪ v \end{matrix} ev case s. (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

Then the induction hypothesis applied to D_{3} yields a deduction of v Value, which is what we needed to show in this case.

Case: If D ends in ev pair we reason similar to cases above.

Case:

D = \begin{matrix}D′ \\ e ↪ ⟨v_{1}, v_{2}⟩ \end{matrix} ev fst. fst e ↪ v_{1}

Then the induction hypothesis applied to D′ yields a deduction P′ of the judgment ⟨v_{1}, v_{2}⟩ Value. By examining the inference rules we can see that P′ must end in an application of the val pair rule, that is,

P′ = \begin{matrix}P_{1} \\ v_{1} Value \end{matrix} \begin{matrix}P_{2} \\ v_{2} Value \end{matrix} val pair ⟨v_{1}, v_{2}⟩ Value

for some P_{1} and P_{2}. Hence v_{1} Value must be derivable, which is what we needed to show. We call this form of argument inversion.

Case: If D ends in ev snd we reason similar to the previous case.

Case: D = \frac{ev lam.}{lam x. e ↪ lam x. e}

Again, this case is immediate, since v = lam x. e is a value by rule val lam.

Case:

D = \begin{matrix}D_{1} \\ e_{1} ↪ lam x. e′_{1} \end{matrix}  \begin{matrix}D_{2} \\ e_{2} ↪ v_{2} \end{matrix}  \begin{matrix}D_{3} \\ [v_{2}/x]e′_{1} ↪ v \end{matrix} ev app. e_{1} e_{2} ↪ v

Then the induction hypothesis on D_{3} yields that v Value.

Case: D ends in ev letv. Similar to the previous case.

Case: D ends in ev letn. Similar to the previous case.

Case:

D = \begin{matrix}D_{1} \\ [fix x. e/x]e ↪ v \end{matrix} ev fix. fix x. e ↪ v

Again, the induction hypothesis on D_{1} directly yields that v is a value.

□

Since it is so pervasive, we briefly summarize the principle of structural induction used in the proof above. We assume we have an arbitrary derivation D of e ↪ v and we would like to prove a property P of D. We show this by induction on the structure of D: For each inference rule in the system defining the judgment e ↪ v we show that the property P holds for the conclusion under the assumption that it holds for every premiss. In the special case of an inference rules with no premisses we have no inductive assumptions; this therefore corresponds to a base case of the induction. This suffices to establish the property P for every derivation D since it must be constructed from the given inference rules. In this particular theorem the property P states that there exists a derivation P of the judgment that v is a value.

2.5 The Type System

In the presentation of the language so far, we have not used types. Thus types are external to the language of expressions and a judgment such as ▷ e : τ may be considered as establishing a property of the (untyped) expression e. This view of types has been associated with Curry [Cur34, CF58], and systems of this style are often called type assignment systems. An alternative is a system in the style of Church [Chu32, Chu33, Chu41], in which types are included within expressions, and every well-typed expression has a unique type. We will discuss such a system in Section 9.1.

Mini-ML as presented by Clément et al. is a language with some limited polymorphism, in that it explicitly distinguishes simple types and type schemes with some restrictions on the use of type schemes. This notion of polymorphism was introduced by Milner [Mil78, DM82]. We will refer to it as schematic polymorphism. In our formulation, we will be able to avoid using type schemes completely by distinguishing two forms of definitions via let, one of which is polymorphic. A formulation in this style orginates with Hannan and Miller [HM89, Han91]. See also ??.

Types τ ::= nat | τ_{1} × τ_{2} | τ_{1} → τ_{2} | α

Here, α stands for type variables. We also need a notion of context which assigns types to free variables in an expression.

Contexts Γ ::= · | Γ, x:τ

We generally omit the empty context, “·”, and, for example, write x:τ for ·, x:τ. We also have to deal again with the problem of variable names. In order to avoid ambiguities and simplify the presentation, we stipulate that each variable may be declared at most once in a context Γ. When we wish to emphasize this assumption, we refer to contexts without repeated variables as valid contexts. We write Γ(x) for the type assigned to x in Γ.

The typing judgment

Γ ▷ e : τ

states that expression e has type τ in context Γ. It is important for the meta-theory that there is exactly one inference rule for each expression constructor. We say that the definition of the typing judgment is syntax-directed. Of course, many deductive systems defining typing judgments are not syntax-directed (see, for example, Section 9.1).

We begin with typing rules for natural numbers. We require that the two branches of a case-expression have the same type τ. This means that no matter which of the two branches of the case-expression applies during evaluation, the value of the whole expression will always have type τ.

\frac{tp z}{Γ ▷ z : nat}  Γ ▷ e : nat ; tp s ; Γ ▷ s e : nat
Γ ▷ e_{1} : nat  Γ ▷ e_{2} : τ  Γ, x:nat ▷ e_{3} : τ

tp case

Γ ▷ (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) : τ

Implicit in the third premiss of the tp case rule is the information that x is a bound variable whose scope is e_{3}. Moreover, x stands for a natural number (the predecessor of the value of e_{1}). Note that we may have to rename the variable x in case another variable with the same name already occurs in the context Γ.

Pairing is straightforward.

Γ ▷ e_{1} : τ_{1} Γ ▷ e_{2} : τ_{2} tp pair

Γ ▷ ⟨e_{1}, e_{2}⟩ : τ_{1} × τ_{2}

Γ ▷ e : τ_{1} × τ_{2} ; tp fst ; Γ ▷ fst e : τ_{1}  Γ ▷ e : τ_{1} × τ_{2} ; tp snd ; Γ ▷ snd e : τ_{2}

Because of the following rule for lam-abstraction, the type of an expression is not unique. This is a characteristic property of a type system in the style of Curry.

Γ, x:τ_{1} ▷ e : τ_{2} tp lam

Γ ▷ lam x. e : τ_{1} → τ_{2}

Γ ▷ e_{1} : τ_{2} → τ_{1} Γ ▷ e_{2} : τ_{2} tp app

Γ ▷ e_{1} e_{2} : τ_{1}

The rule tp lam is (implicitly) restricted to the case where x does not already occur in Γ, since we made the general assumption that no variable occurs more than once in a context. This restriction can be satisfied by renaming the bound variable x, thus allowing the construction of a typing derivation for ▷ lam x. lam x. x : α → (β → β), but not for ▷ lam x. lam x. x : α → (β → α). Note that together with this rule, we need a rule for looking up variables in the context.

\begin{aligned}Γ(x) = τ \\ & tp var \\ Γ ▷ x : τ \end{aligned}

As variables occur at most once in a context, this rule does not lead to any inherent ambiguity.

Our language incorporates a let val expression to compute intermediate values. This is not strictly necessary, since it may be defined using lam-abstraction and application (see Exercise 2.19).

Γ ▷ e_{1} : τ_{1} Γ, x:τ_{1} ▷ e_{2} : τ_{2} tp letv

Γ ▷ let val x = e_{1} in e_{2} : τ_{2}

Even though e_{1} may have more than one type, only one of these types (τ_{1}) can be used for occurrences of x in e_{2}. In other words, x can not be used polymorphically, that is, at various types.

Schematic polymorphism (or ML-style polymorphism) only plays a role in the typing rule for let name. What we would like to achieve is that, for example, the following judgment holds:

▷ let name f = lam x. x in ⟨f z, f (lam y. s y)⟩ : nat × (nat → nat)

Clearly, the expression can be evaluated to ⟨z, (lam y. s y)⟩, since lam x. x can act as the identity function on any type, that is, both

▷ lam x. x : nat → nat,
and ▷ lam x. x : (nat → nat) → (nat → nat)

are derivable. In a type system with explicit polymorphism a more general judgment might be expressed as ▷ lam x. x : ∀α. α → α (see Section ??). Here, we use a different device by allowing different types to be assigned to e_{1} at different occurrences of x in e_{2} when type-checking let name x = e_{1} in e_{2}. We achieve this by substituting e_{1} for x in e_{2} and checking only that the result is well-typed.

Γ ▷ e_{1} : τ_{1} Γ ▷ [e_{1}/x]e_{2} : τ_{2}

tp letn

Γ ▷ let name x = e_{1} in e_{2} : τ_{2}

Note that τ_{1}, the type assigned to e_{1} in the first premiss, is not used anywhere. We require such a derivation nonetheless so that all subexpressions of a well-typed term are guaranteed to be well-typed (see Exercise 2.20). The reader may want to check that with this rule, the example above is indeed well-typed.

Finally we come to the typing rule for fixpoint expressions. In the evaluation rule, we substitute [fix x. e/x]e in order to evaluate fix x. e. For this to be welltyped, the body e must be well-typed under the assumption that the variable x has the type of whole fixpoint expression. Thus we are lead to the rule

Γ, x:τ ▷ e : τ tp fix.

Γ ▷ fix x. e : τ

More general typing rules for fixpoint constructs have been considered in the literature, most notably the rule of the Milner-Mycroft calculus which is discussed in Exercise 2.21.

An important property of the system is that an expression uniquely determines the last inference rule of its typing derivation. This leads to a principle of inversion: from the type of an expression we can draw conclusions about the types of its constituents expressions. The inversion principle is used pervasively in the proof of Theorem 2.5, for example. In many deductive systems similar inversion principles hold, though often they turn out to be more difficult to prove.

Lemma 2.2 (Inversion) Given a context Γ and an expression e such that there exists a τ such that Γ ▷ e : τ is derivable. Then the last inference rule of any derivation of Γ ▷ e : τ′ for some τ′ is uniquely determined.

Proof: By inspection of the inference rules. □

Note that this does not imply that types are unique; they are not in this formulation of Mini-ML as remarked above.

2.6 Type Preservation

Before we come to the statement and proof of type preservation in Mini-ML, we need a few preparatory lemmas. The reader may wish to skip ahead and reexamine these lemmas wherever they are needed. We first note the properties of exchange and weakening and then state and prove a substitution lemma for typing derivations. Substitution lemmas are basic to the investigation of many deductive systems, and we will pay special attention to them when considering the representation of proofs of meta-theorems in a logical framework. We use the notation Γ, Γ′ for the result of appending the declarations in Γ and Γ′ assuming implicitly that the result is valid. Recall that a context is valid if no variable in it is declared more than once.

Lemma 2.3 (Exchange and Weakening)

1. If valid contexts Γ and Γ′ only differ in the order of declarations, then Γ ▷ e : τ if and only if Γ′ ▷ e : τ.

2. If Γ ▷ e : τ then Γ, Γ′ ▷ e : τ provided Γ, Γ′ is a valid context.

Proof: By straightforward inductions over the structure of the derivation of Γ ▷ e : τ. The only inference rule where the context is examined is tp var which will be applicable if a declaration x:τ is present in the context Γ. It is clear that the order of declarations or the presence of additional non-conflicting declarations does not alter this property. □

Type derivations which differ only by exchange or weakening in the type declarations Γ have identical structure. Thus we permit the exchange or weakening of type declarations in Γ during a structural induction over a typing derivation. The substitution lemma below is also central, it is closely connected to the notions of parametric and hypothetical judgments introduced in Chapter 5.

Lemma 2.4 (Substitution) If Γ ▷ e′ : τ′ and Γ, x:τ′ ▷ e : τ, then Γ ▷ [e′/x]e : τ.

Proof: By induction over the structure of the derivation D :: Γ, x:τ′ ▷ e : τ. The result should be intuitive: wherever x occurs in e we are at a leaf in the typing derivation of e. After substitution of e′ for x, we have to supply a derivation showing that e′ has type τ′ at this leaf position, which exists by assumption. We only show a few cases in the proof in detail; the remaining ones follow the same pattern.

Case: D = \begin{aligned}(Γ, x:τ′)(x) = τ′ \\ & tp var. \\ Γ, x:τ′ ▷ x : τ′ \end{aligned}

Then [e′/x]e = [e′/x]x = e′, so the lemma reduces to showing Γ ▷ e′ : τ′ from Γ ▷ e′ : τ′.

Case: D = \begin{aligned}(Γ, x:τ′)(y) = τ \\ & tp var, \\ Γ, x:τ′ ▷ y : τ \end{aligned} where x ≠ y.

In this case, [e′/x]e = [e′/x]y = y and hence the lemma follows from

\begin{aligned}(Γ)(y) = τ \\ & tp var. \\ Γ ▷ y : τ \end{aligned}

Case: D = \begin{matrix}D_{1} \\ Γ, x:τ′ ▷ e_{1} : τ_{2} → τ_{1} \end{matrix} \begin{matrix}D_{2} \\ Γ, x:τ′ ▷ e_{2} : τ_{2} \end{matrix} tp app. Γ, x:τ′ ▷ e_{1} e_{2} : τ_{1}

Then we construct a deduction

\begin{matrix}E_{1} \\ Γ ▷ [e′/x]e_{1} : τ_{2} → τ_{1} \end{matrix}  \begin{matrix}E_{2} \\ Γ ▷ [e′/x]e_{2} : τ_{2} \end{matrix}

tp app

Γ ▷ ([e′/x]e_{1}) ([e′/x]e_{2}) : τ_{1}

where E_{1} and E_{2} are known to exist from the induction hypothesis applied to D_{1} and D_{2}, respectively. By definition of substitution, [e′/x](e_{1} e_{2}) = ([e′/x]e_{1}) ([e′/x]e_{2}), and the lemma is established in this case.

Case: D = \begin{matrix}D_{1} \\ Γ, x:τ′, y:τ_{1} ▷ e_{2} : τ_{2} \end{matrix} tp lam Γ, x:τ′ ▷ lam y. e_{2} : τ_{1} → τ_{2}.

In this case we need to use Exchange and then apply the induction hypothesis by using Γ, y:τ_{1} for Γ. This is why the lemma is formulated using arbitrary valid contexts Γ: an attempt to prove it inductively for expressions e′ whose only free variable is x will fail. From the induction hypothesis and one inference step we obtain

\begin{matrix}E_{1} \\ Γ, y:τ_{1} ▷ [e′/x]e_{2} : τ_{2} \end{matrix}

tp lam

Γ ▷ lam y. [e′/x]e_{2} : τ_{1} → τ_{2}

which yields the lemma by the equation [e′/x](lam y. e_{2}) = lam y. [e′/x]e_{2} if y is not free in e′ and distinct from x. We can assume that these conditions are satisfied, since they can always be achieved by renaming bound variables.

□

The statement of the type preservation theorem below is written in such a way that the induction argument will work directly.

Theorem 2.5 (Type Preservation) For any e and v, if e ↪ v is derivable, then for any τ such that ▷ e : τ is derivable, ▷ v : τ is also derivable.

Proof: By induction on the structure of the deduction D of e ↪ v. The justification “by inversion” refers to Lemma 2.2. More directly, from the form of the judgment established by a derivation we draw conclusions about the possible forms of the premiss, which, of course, must also derivable.

Case: D = \frac{ev z.}{z ↪ z}

Then we have to show that for any type τ such that ▷ z : τ is derivable, ▷ z : τ is derivable. This is obvious.

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ v_{1} \end{matrix} ; ev s. ; s e_{1} ↪ s v_{1} Then

▷ s e_{1} : τ  By assumption
▷ e_{1} : nat and τ = nat  By inversion
▷ v_{1} : nat  By ind. hyp. on D_{1}
▷ s v_{1} : nat  By rule tp s

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ z \end{matrix}  \begin{matrix}D_{2} \\ e_{2} ↪ v \end{matrix} ev case z. (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

▷ (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) : τ  By assumption
▷ e_{2} : τ  By inversion
▷ v : τ  By ind. hyp. on D_{2}

2.6. TYPE PRESERVATION 27

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ s v′_{1} \end{matrix} \begin{matrix}D_{3} \\ [v′_{1}/x]e_{3} ↪ v \end{matrix} ev case s. (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) ↪ v

▷ (case e_{1} of z ⇒ e_{2} | s x ⇒ e_{3}) : τ  By assumption
x:nat ▷ e_{3} : τ  By inversion
▷ e_{1} : nat  By inversion
▷ s v′_{1} : nat  By ind. hyp. on D_{1}
▷ v′_{1} : nat  By inversion
▷ [v′_{1}/x]e_{3} : τ  By the Substitution Lemma 2.4
▷ v : τ  By ind. hyp. on D_{3}

Cases: If D ends in ev pair, ev fst, or ev snd we reason similar to cases above (see

Exercise 2.15). Case: D = \frac{ev lam.}{lam x. e ↪ lam x. e}

This case is immediate as for ev z.

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ lam x. e′_{1} \end{matrix} \begin{matrix}D_{2} \\ e_{2} ↪ v_{2} \end{matrix} \begin{matrix}D_{3} \\ [v_{2}/x]e′_{1} ↪ v \end{matrix} ev app. e_{1} e_{2} ↪ v

▷ e_{1} e_{2} : τ  By assumption
▷ e_{1} : τ_{2} → τ and ▷ e_{2} : τ_{2} for some τ_{2}  By inversion
▷ lam x. e′_{1} : τ_{2} → τ  By ind. hyp. on D_{1}
x:τ_{2} ▷ e′_{1} : τ  By inversion
▷ v_{2} : τ_{2}  By ind. hyp. on D_{2}
▷ [v_{2}/x]e′_{1} : τ  By the Substitution Lemma 2.4
▷ v : τ  By ind. hyp. on D_{3}

Case: D = \begin{matrix}D_{1} \\ e_{1} ↪ v_{1} \end{matrix} \begin{matrix}D_{2} \\ [v_{1}/x]e_{2} ↪ v \end{matrix} ev letv. let val x = e_{1} in e_{2} ↪ v

▷ let val x = e_{1} in e_{2} : τ  By assumption
▷ e_{1} : τ_{1} and x:τ_{1} ▷ e_{2} : τ for some τ_{1}  By inversion
▷ v_{1} : τ_{1}  By ind. hyp. on D_{1}
▷ [v_{1}/x]e_{2} : τ  By the Substitution Lemma 2.4
▷ v : τ  By ind. hyp. on D_{2}

Case: D = \begin{matrix}D_{2} \\ [e_{1}/x]e_{2} ↪ v \end{matrix} ev letn. let name x = e_{1} in e_{2} ↪ v

▷ let name x = e_{1} in e_{2} : τ  By assumption
▷ [e_{1}/x]e_{2} : τ  By inversion
▷ v : τ  By ind. hyp. on D_{2}

Case: D = \begin{matrix}D_{1} \\ [fix x. e_{1}/x]e_{1} ↪ v \end{matrix} ev fix. fix x. e_{1} ↪ v

▷ fix x. e_{1} : τ  By assumption
x : τ ▷ e_{1} : τ  By inversion
▷ [fix x. e_{1}/x]e_{1} : τ  By the Substitution Lemma 2.4
▷ v : τ  By ind. hyp. on D_{1}
□

It is important to recognize that this theorem cannot be proved by induction on the structure of the expression e. The difficulty is most pronounced in the cases for let and fix: The expressions in the premisses of these rules are in general much larger than the expressions in the conclusion. Similarly, we cannot prove type preservation by an induction on the structure of the typing derivation of e.

2.7 Further Discussion

Ignoring details of concrete syntax, the Mini-ML language is completely specified by its typing and evaluation rules. Consider a simple simple model of an interaction with an implementation of Mini-ML consisting of two phases: type-checking and evaluation. During the first phase the implementation only accepts expressions e that are well-typed in the empty context, that is, ▷ e : τ for some τ. In the second phase the implementation constructs and prints a value v such that e ↪ v is derivable. This model is simplistic in some ways, for example, we ignore the question which values can actually be printed or observed by the user. We will return to this point in Section ??.

Our self-contained language definition by means of deductive systems does not establish a connection between types, values, expressions, and mathematical objects such as partial functions. This can be seen as the subject of denotational semantics. For example, we understand intuitively that the expression

ss = lam x. s (s x)

denotes the function from natural numbers to natural numbers that adds 2 to its argument. Similarly,

pred_{0} = lam x. case x of z ⇒ fix y. y | s x′ ⇒ x′

denotes the partial function from natural numbers to natural numbers that returns the predecessor of any argument greater or equal to 1 and is undefined on 0. But is this intuitive interpretation of expressions justified? As a first step, we establish that the result of evaluation (if one exists) is unique.

Theorem 2.6 (Uniqueness of Values) If e ↪ v_{1} and e ↪ v_{2} are derivable then v_{1} = v_{2}.^{3}

Proof: Straightforward (see Exercise 2.16). □

Intuitively the type nat can be interpreted by the set of natural numbers. We write v_{nat} for values v such that ▷ v : nat. It can easily be seen by induction on the structure of the derivation of v_{nat} Value that v_{nat} could be defined inductively by

v_{nat} ::= z | s v_{nat}.

The meaning or denotation of a value v_{nat}, [[v_{nat}]], can be defined almost trivially as

[[z]] = 0
[[s v_{nat}]] = [[v_{nat}]] + 1.

It is immediate that this is a bijection between closed values of type nat and the natural numbers. The meaning of an arbitrary closed expression e_{nat} of type nat can then be defined by

[[e_{nat}]] = \{ \begin{matrix}[[v]] & if e_{nat} ↪ v is derivable \\ undefined & otherwise \end{matrix}

Determinism of evaluation (Theorem 2.6) tells us that v, if it exists, is uniquely defined. Value soundness 2.1 tells us that v is indeed a value. Type preservation (Theorem 2.5) tells us that v will be a closed expression of type nat and thus that the meaning of an arbitrary expression of type nat, if it is defined, is a unique natural number. Furthermore, we are justified in overloading the [[·]] notation for values and arbitray expressions, since values evaluate to themselves (Exercise 2.13).

Next we consider the meaning of expressions of functional type. Intuitively, if ▷ e : nat → nat, then the meaning of e should be a partial function from natural numbers to natural numbers. We define this as follows:

[[e]](n) = \{ \begin{matrix}[[v_{2}]] & if e v_{1} ↪ v_{2} and [[v_{1}]] = n \\ undefined & otherwise \end{matrix}

3Recall that expressions which differ only in the names of their bound variables are considered equal.

This definition is well-formed by reasoning similar to the above, using the observation that [[·]] is a bijection between closed values of type nat and natural numbers.

Thus we were justified in thinking of the type nat → nat as consisting of partial functions from natural numbers to natural numbers. Partial functions in mathematics are understood in terms of their input/output behavior rather than in terms of their concrete definition; they are viewed extensionally. For example, the expressions

ss = lam x. s (s x) and
ss′ = fix f. lam x. case x of z ⇒ s (s z) | s x′ ⇒ s (f x′)

denote the same function from natural numbers to natural numbers: [[ss]] = [[ss′]]. Operationally, of course, they have very different behavior. Thus denotational semantics induces a non-trivial notion of equality between expressions in our language. On the other hand, it is not immediately clear how to take advantage of this equality due to its non-constructive nature. The notion of extensional equality between partial recursive function is not recursively axiomatizable and therefore we cannot write a complete deductive system to prove functional equalities. The denotational approach can be extended to higher types (for example, functions that map functions from natural numbers to natural numbers to functions from natural numbers to natural numbers) in a natural way.

It may seem from the above development that the denotational semantics of a language is uniquely determined. This is not the case: there are many choices. Especially the mathematical domains we use to interpret expressions and the structure we impose on them leave open many possibilites. For more on the subject of denotational semantics see, for example, [Gun92].

In the approach above, the meaning of an expression depends on its type. For example, for the expression id = lam x. x we have ▷ id : nat → nat and by the reasoning above we can interpret it as a function from natural numbers to natural numbers. We also have ▷ id : (nat → nat) → (nat → nat), so it also maps every function between natural numbers to itself. This inherent ambiguity is due to our use of Curry’s approach where types are assigned to untyped expressions. It can be remedied in two natural ways: we can construct denotations independently of the language of types, or we can give meaning to typing derivations. In the first approach, types can be interpreted as subsets of a universe from which the meanings of untyped expressions are drawn. The disadvantage of this approach is that we have to give meanings to all expressions, even those that are intuitively meaningless, that is, ill-typed. In the second approach, we only give meaning to expressions that have typing derivations. Any possible ambiguity in the assignment of types is resolved, since the typing derivation will choose are particular type for the expression. On the other hand we may have to consider coherence: different typing derivations for the same expression and type should lead to the same meaning. At the very least the meanings should be compatible in some way so that arbitrary decisions made during type inference do not lead to observable differences in the behavior of a program. In the Mini-ML language we discussed so far, this property is easily seen to hold, since an expression uniquely determines its typing derivation. For more complex languages this may require non-trivial proof. Note that the ambiguity problem does not usually arise when we choose a language presentation in the style of Church where each expression contains enough type information to uniquely determine its type.

2.8 Exercises

Exercise 2.1 Write Mini-ML programs for multiplication, exponentiation, and a function which returns a pair of (integer) quotient and remainder of two natural numbers.

Exercise 2.2 The principal type of an expression e is a type τ such that any type τ′ of e can be obtained by instantiating the type variables in τ. Even though types in our formulation of Mini-ML are not unique, every well-typed expression has a principal type [Mil78]. Write Mini-ML programs satisfying the following informal specifications and determine their principal types.

1. compose f g to compute the composition of two functions f and g.

2. iterate n f x to iterate the function f n times over x.

Exercise 2.3 Write down the evaluation of plus_{2} (s z) (s z), given the definition of plus_{2} in the example on page 11.

Exercise 2.4 Write out the typing derivation which shows that the function double on page 16 is well-typed.

Exercise 2.5 Explore a few alternatives to the definition of expressions given in Section 2.1. In each case, give the relevant inference rules for evaluation and typing.

1. Add a type of Booleans and replace the constructs concerning natural numbers

by

e ::= … | z | s e | pred e | zerop e

2. Replace the constructs concerning pairs by

e ::= … | pair | fst | snd

3. Replace the constructs concerning pairs by

e ::= … | ⟨e_{1}, e_{2}⟩ | split e_{1} as ⟨x_{1}, x_{2}⟩ ⇒ e_{2}

Exercise 2.6 Consider an extension of the language by the unit type 1 (often written as unit) and disjoint sums τ_{1} + τ_{2}:

τ ::= … | 1 | (τ_{1} + τ_{2})
e ::= … | ⟨ ⟩ | inl e | inr e | (case e_{1} of inl x_{2} ⇒ e_{2} | inr x_{3} ⇒ e_{3})

For example, an alternative to the predecessor function might return ⟨ ⟩ if the argument is zero, and the predecessor otherwise. Because of the typing discipline, the expression

pred′ = lam x. case x of z ⇒ ⟨ ⟩ | s x′ ⇒ x′

is not typable. Instead, we have to inject the values into a disjoint sum type:

pred′ = lam x. case x of z ⇒ inl ⟨ ⟩ | s x′ ⇒ inr x′

so that ▷ pred′ : nat → (1 | nat)

Optional values of type τ can be modelled in general by using the type (1 | τ).

1. Give appropriate rules for evaluation and typing.

2. Extend the notion of value.

3. Extend the proof of value soundness (Theorem 2.1).

4. Extend the proof type preservation (Theorem 2.5).

Exercise 2.7 Consider a language extension

τ ::= … | τ^{∗}.

where τ^{∗} is the type of lists whose members have type τ. Introduce appropriate value constructor and discriminator expressions and proceed as in Exercise 2.6.

Exercise 2.8 In this exercise we explore the operation of substitution in some more detail than in section 2.2.

1. Define x free in e which should hold when the variable x occurs free in e.

2. Define e =_{α} e′ which should hold when e and e′ are alphabetic variants, that is, they differ only in the names assigned to their bound variables as explained in Section 2.2.

3. Define [e′/x]e, the result of substituting e′ for x in e. This operation should avoid capture of variables free in e′ and the result should be unique up to renaming of bound variables.

4. Prove [e′/x]e =_{α} e if x does not occur free in e′.

5. Prove [e_{2}/x_{2}]([e_{1}/x_{1}]e) =_{α} [([e_{2}/x_{2}]e_{1})/x_{1}]([e_{2}/x_{2}]e), provided x_{1} does not occur free in e_{2}.

Exercise 2.9 In this exercise we will explore different ways to treat errors in the semantics.

1. Assume there is a new value error of arbitary type and modify the operational semantics appropriately. You may assume that only well-typed expressions are evaluated. For example, evaluation of s (lam x. x) does not need to result in error.

2. Add an empty type 0 (often called void) containing no values. Are there any closed expressions of type 0? Add a new expression form any e which has arbitrary type τ whenever e has type 0, but add no evaluation rules for any. Do the value soundness and type preservation properties extend to this language? How does this language compare to the one in item 1.

3. An important semantic property of type systems is often summarized as “welltyped programs cannot go wrong.” The meaning of ill-typed expressions such as fst z would be defined as a distinguished semantic value wrong (in contrast to intuitively non-terminating expressions such as fix x. x) and it is then shown that no well-typed expression has meaning wrong. A related phrase is that in statically typed languages “no type-errors can occur at runtime.” Discuss how these properties might be expressed in the framework presented here and to what extent they are already reflected in the type preservation theorem.

Exercise 2.10 In the language Standard ML [MTH90], occurrences of fixpoint expressions are syntactially restricted to the form fix x. lam y. e. This means that evaluation of a fixpoint expression always terminates in one step with the value lam y. [fix x. lam y. e/x]e.

It has occasionally been proposed to extend ML so that one can construct recursive values. For example, ω = fix x. s x would represent a “circular value” s (s …) which could not be printed finitely. The same value could also be defined, for example, as ω′ = fix x. s (s x).

In our language, the expressions ω and ω′ are not values and, in fact, they do not even have a value. Intuitively, their evaluation does not terminate.

Define an alternative semantics for the Mini-ML language that permits recursive values. Modify the definition of values and the typing rules as necessary. Sketch the required changes to the statements and proofs of value soundness, type preservation, and uniqueness of values. Discuss the relative merits of the two languages.

Exercise 2.11 Explore an alternative operational semantics in which expressions that are known to be values (since they have been evaluated) are not evaluated again. State and prove in which way the new semantics is equivalent to the one given in Section 2.3. Hint: It may be necessary to extend the language of expressions or explicitly separate the language of values from the language of expressions.

Exercise 2.12 Specify a call-by-name operational semantics for our language, where function application is given by

e_{1} ↪ lam x. e′_{1} [e_{2}/x]e′_{1} ↪ v ev app

e_{1} e_{2} ↪ v

Furthermore, we would like constructors (successor and pairing) to be lazy, that is, they should not evaluate their arguments. Consider if it still makes sense to have let val and let name and what their respective rules should be. Modify the affected inference rules, the definition of a value, and the proof that evaluation returns a value.

Exercise 2.13 Prove that v Value is derivable if and only if v ↪ v is derivable. That is, values are exactly those expressions which evaluate to themselves.

Exercise 2.14 A replacement lemma is necessary in some formulations of the type preservation theorem. It states:

If, for any type τ′, ▷ e′_{1} : τ′ implies ▷ e′_{2} : τ′, then ▷ [e′_{1}/x]e : τ implies
▷ [e′_{2}/x]e : τ.

Prove this lemma. Be careful to generalize as necessary and clearly exhibit the structure of the induction used in your proof.

Exercise 2.15 Complete the proof of Theorem 2.5 by giving the cases for ev pair, ev fst, and ev snd.

Exercise 2.16 Prove Theorem 2.6.

Exercise 2.17 (Non-Determinism) Consider a non-deterministic extension of Mini-ML with two new expression constructors ∘ and e_{1} ⊕ e_{2} with the evaluation rules

e_{1} ↪ v ; ev choice_{1} ; e_{1} ⊕ e_{2} ↪ v  e_{2} ↪ v ; ev choice_{2} ; e_{1} ⊕ e_{2} ↪ v

Thus, ⊕ signifies non-deterministic choice, while ∘ means failure (choice between zero alternatives).

1. Modify the type system and extend the proofs of value soundness and type preservation.

2. Write an expression that may evaluate to an arbitrary natural number.

3. Write an expression that may evaluate precisely to the numbers that are not prime.

4. Write an expression that may evaluate precisely to the prime numbers.

Exercise 2.18 (General Pattern Matching) Patterns for Mini-ML can be defined by

Patterns p ::= x | z | s p | ⟨p_{1}, p_{2}⟩.

Devise a version of Mini-ML where case (for natural numbers), fst, and snd are replaced by a single form of case-expression with arbitrarily many branches. Each branch has the form p ⇒ e, where the variables in p are bound in e.

1. Define an operational semantics.

2. Define typing rules.

3. Prove type preservation and any lemmas you may need. Show only the critical cases in proofs that are very similar to the ones given in the notes.

4. Is your language deterministic? If not, devise a restriction which makes your language deterministic.

5. Does your operational semantics require equality on expressions of functional type? If yes, devise a restriction that requires equality only on observable types—in this case (inductively) natural numbers and products of observable type.

Exercise 2.19 Prove that the expressions let val x = e_{1} in e_{2} and (lam x. e_{2}) e_{1} are equivalent in sense that

1. for any context Γ, Γ ▷ let val x = e_{1} in e_{2} : τ iff Γ ▷ (lam x. e_{2}) e_{1} : τ, and

2. let val x = e_{1} in e_{2} ↪ v iff (lam x. e_{2}) e_{1} ↪ v.

Is this sufficient to guarantee that if we replace one expression by the other somewhere in a larger program, the value of the whole program does not change?

Exercise 2.20 Carefully define a notion of subexpression for Mini-ML and prove that if Γ ▷ e : τ then every subexpression e′ of e is also well-typed in an appropriate context.

Exercise 2.21 (Milner-Mycroft Typing Rules)

36 CHAPTER 2. THE MINI-ML LANGUAGE

[ An exercise about mutual recursion and the Milner-Mycroft rules. ] Exercise 2.22 (Polymorphism)

[ An exercise about prefix polymorphism in the style of ML. ] Exercise 2.23 [ An exercise about catch and throw. ]