A Theory of Type Polymorphism in Programming

About this paperTiếng Việtbeside Tiếng Việt

This paper is under a licence that does not allow it to be reproduced, so what is here is its front matter and a short abstract. Read it at the publisher's page.

Edinburgh Research Explorer

A theory of type polymorphism in programming

Citation for published version: Milner, R 1978, 'A theory of type polymorphism in programming', Journal of Computer and System Sciences, vol. 17, no. 3, pp. 348-375. https://doi.org/10.1016/0022-0000(78)90014-4

Digital Object Identifier (DOI): 10.1016/0022-0000(78)90014-4

Link: Link to publication record in Edinburgh Research Explorer

Document Version: Publisher's PDF, also known as Version of record

Published In: Journal of Computer and System Sciences

General rights Copyright for the publications made accessible via the Edinburgh Research Explorer is retained by the author(s) and / or other copyright owners and it is a condition of accessing these publications that users recognise and abide by the legal requirements associated with these rights.

Take down policy The University of Edinburgh has made every reasonable effort to ensure that Edinburgh Research Explorer content complies with UK legislation. If you believe that the public display of this file breaches copyright please contact openaccess@ed.ac.uk providing details, and we will remove access to the work immediately and investigate your claim.

A Theory of Type Polymorphism in Programming

ROBIN MILNER

Computer Science Department, University of Edinburgh, Edinburgh, Scotland

Received October 10, 1977; revised April 19, 1978

The aim of this work is largely a practical one. A widely employed style of programming, particularly in structure-processing languages which impose no discipline of types, entails defining procedures which work well on objects of a wide variety. We present a formal type discipline for such polymorphic procedures in the context of a simple programming language, and a compile time type-checking algorithm W\mathcal{W} which enforces the discipline. A Semantic Soundness Theorem (based on a formal semantics for the language) states that well-type programs cannot "go wrong" and a Syntactic Soundness Theorem states that if W\mathcal{W} accepts a program then it is well typed. We also discuss extending these results to richer languages; a type-checking algorithm based on W\mathcal{W} is in fact already implemented and working, for the metalanguage ML in the Edinburgh LCF system.

1 INTRODUCTION

The aim of this work is largely a practical one. A widely employed style of programming, particularly in structure-processing languages which impose no discipline of types (LISP is a perfect example), entails defining procedures which work well on objects of a wide variety (e.g., on lists of atoms, integers, or lists). Such flexibility is almost essential in this style of programming; unfortunately one often pays a price for it 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. On the other hand a type discipline such as that of ALGOL 68 [22] which precludes the flexibility mentioned above, also precludes the programming style which we are talking about. ALGOL 60 was more flexible—in that it required procedure parameters to be specified only as "procedure" (rather than say "integer to real procedure")—but the flexibility was not uniform, and not sufficient.

An early discussion of such flexibility can be found in Strachey [19], who was probably the first to call it polymorphism. In fact he qualified it as "parametric" polymorphism, in contrast to what he called "adhoc" polymorphism. An example of the latter is the use of "+*" to denote both integer and real addition (in fact it may be further extended to denote complex addition, vector addition, etc.); this use of an identifier at several distinct types is often now called "overloading," and we are not concerned with it in this paper.

In this paper then, we present and justify one method of gaining type flexibility, but also retaining a discipline which ensures robust programs. We have evidence that this work is not just a theoretical exercise; the polymorphic type discipline which we discuss here has been incorporated in the LCF metalanguage ML [2, 3], and has been in use for nearly 2 years. The compile-time type checker for this language has proved to be a valuable filter which traps a significant proportion of programming errors.

The main body of the present paper is concerned with a technical account—both semantic and syntactic—of our discipline of types in the context of a simple illustrative language, but at this point it is helpful to characterize the approach informally. We outline its predominant features.

First, everything concerning types is done at compile time; once the type checker (part of the compiler) has accepted a program or program phrase, code may be generated which assumes that no objects carry their types at run-time. This is widely accepted as yielding efficient object code, though it does impose constraints on the use of types compared with, for example, the approach in EL1 [21].

Second, many nontrivial programs can avoid mentioning types entirely, since they be inferred from context. (In ML however, as in other languages, the user may—indeed often should—define his own types together with operations over these types. Recent languages which allow the user to define his own types in this manner are CLU [8], ALPHARD [23] and Euclid [6]). Although it can be argued convincingly that to demand type specification for declared variables, including the formal parameters of procedures, leads to more intelligible problems, it is also convenient—particularly in on-line programming—to be able to leave out these specifications. In any case, the type checker which we present is quite simple and could not be made much simpler even if the types of variables were always specified in declarations.

Third, polymorphism plays a leading role. For example, a procedure is assigned a polymorphic type (which we abbreviate to polytype) in general; only when the types of its arguments and result can be uniquely determined from the context is it monomorphic (i.e., assigned a monotype). Gries and Gehani [4], among others, have made a convincing case for controlled polymorphic programming (in contrast with the typeless programming in LISP or in SNOBOL); for them however, and also for Tennent [20], the presence of type variables or identifiers is needed to specify polymorphic types. For us, the polymorphism present in a program is a natural outgrowth of the primitive polymorphic operators which appear to exist in every programming language; such operators are assignment, function application, pairing and tupling, and list-processing operators. It is principally the type constraints on these operators, and in the declaration and use of variables, which determine for us the types of a program phrase and its subphrases.

We do not discuss in this paper—except briefly at the end—either coercions or the "overloading" of identifiers. Our view is that these concepts, and also run-time type manipulation, are somewhat orthogonal to a compile-time polymorphic type discipline, and may (to some extent) be incorporated without invalidating it.

In Section 2 we illustrate our type discipline by examples in a fragment of ML. This fragment should be self-explanatory, but an outline of ML is given in [3] and a full description appears in [2]. These illustrations should serve to make the point that we are able to handle useful languages. The remainder of the paper justifies the discipline using a very simple applicative language, Exp. The justification factors into two parts.

In Section 3 we define the notion of well typing (correct type assignment) and prove the Semantic Soundness Theorem, which says that a well-typed program is semantically free of type violation. If we were to give an operational definition of the language, this would imply that, for example, an integer is never added to a truth value or applied to an argument, and consequently need not carry its type around for run-time checking. In Section 4 we present a well-type algorithm W\mathcal{W} and prove the Syntactic Soundness Theorem, which states that W\mathcal{W}, if it succeeds, produces a well typing of a program. We also give a more efficient algorithm J\mathcal{J}, which simulates W\mathcal{W}.

The types in Exp are just the hierarchy of purely functional types over a set of basic types. That is, the polymorphism in Exp is the natural outgrowth of a single primitive polymorphic operator, function application, together with variable binding. To add other primitive polymorphic operators, such as pairing and list-processing operators (as in ML), together with types built from basic ones ×\times (Cartesian Product), list (list-forming), and ++ (disjoint sum) in addition to (function type), presents no extra difficulty in the two soundness theorems. Indeed, adding an assignment operator is also easy as far as the Syntactic Soundness Theorem is concerned, but the Semantic Soundness Theorem is harder to extend in this case, due to the extra semantic complication of a memory or store which holds the current values of assignable variables. We discuss this further in Section 5.

Our work is a step towards solving the problem expressed by Morris [10] in his thesis as follows: "to design a language and a type system in which a programmer may define functions whose parameters may have different types for different calls of the function." We recommend Chapter 4 of this thesis as a lucid introduction to the problem. Although Morris does not discuss the semantics of types formally, or give a polymorphic type system, he describes how a valid type assignment may be found for a term of the λ\lambda-calculus by solving a set of simultaneous linear equations; we take this idea further in the next section.

After doing this work we became aware of Hindley's [5] method for deriving the "principal type scheme" (which is what we call a polymorphic type) for a term in combinatory logic. Hindley appears to have been the first to notice that the Unification Algorithm of Robinson [14] is appropriate to this problem. Our work can be regarded as an extension of Hindley's method to programming languages with local declarations, and as a semantic justification of the method.

In summary, we present a polymorphic type discipline which is syntactically well understood and justified for a currently used programming language with imperative features, and is also semantically explained for a nontrivial, though nonimperative, sublanguage.

2 ILLUSTRATIONS OF THE TYPE DISCIPLINE

We illustrate our notion of polymorphism by means of some simple examples. They are written in a fragment of ML which we hope is self-explanatory; this fragment is indeed no more than Landins ISWIM [7], and we refer the reader to Burge's book [1] in which he uses this style of programming almost exactly. We use no imperative constructs here (assignments or jumps). The constructs

let x=e in e′,let f(x1,...,xn)=e in e′let\ x = e\ in\ e', \\ let\ f(x_1, ..., x_n) = e\ in\ e'

are used to give xx the value of ee, and to give ff the value of the abstraction λ(x1,...,xn)⋅e\lambda(x_1, ..., x_n) \cdot e, throughout e′e'. For recursive functions letrec is used in place of let, and when the part in e' is omitted we have a declaration.

The fully determined types (i.e., the monotypes) of ML are built from a set of basic types (int, bool, etc.) by the binary infix operators ×\times (Cartesian product), ++ (disjoint sum) and →\rightarrow (function type), and the unary postfixed operator list. Polymorphic types (polytypes) are obtained by admitting type variables, which here are represented by α,β,γ⋯\alpha, \beta, \gamma \cdots. We represent arbitrary types by ρ,σ,τ\rho, \sigma, \tau. For this section we leave the meaning of types to the reader's intuition; it is made precise in the next section.

Example 1. Mapping a function over a list.

letrec map(f,m)=if null(m) then nilelse cons(f(hd(m)),map(f,tl(m))).letrec\ map(f, m) = if\ null(m)\ then\ nil \\ else\ cons(f(hd(m)), map(f, tl(m))).

Intuitively, the function map so declared takes a function from things of one sort to things of another sort, and a list of things of the first sort, and produces a list of things of the second sort. So we say that map has type

((α→β)×α list)→β list,((\alpha \rightarrow \beta) \times \alpha\ list) \rightarrow \beta\ list,

where α,β\alpha, \beta are type variables.

How is this type determined from the bare declaration of map? First, the generic types (we discuss "generic" later) of the identifiers occurring free in the declaration are given by null: α list→bool\alpha\ list \rightarrow bool, nil: α list\alpha\ list, hd: α list→α\alpha\ list \rightarrow \alpha, tl: α list→α list\alpha\ list \rightarrow \alpha\ list, cons: (α×α list)→α list(\alpha \times \alpha\ list) \rightarrow \alpha\ list, that is, they are polymorphic, because their types contain one or more type variables, and our rule is: To every occurrence of such an identifier is assigned a type which is a substitution instance (substituting types for type variables) of its generic type.

Now each of these identifiers occurs just once in the declaration, so if we denote by σid\sigma_{id} the type assigned to an identifier id we must have for some types τ1,...,τ5\tau_1, ..., \tau_5,

σnull=τ1 list→bool,σnil=τ2 list,σhd=τ3 list→τ3,σtl=τ4 list→τ4 list,σcons=(τ5×τ5 list)→τ5 list.\begin{align*} \sigma_{null} &= \tau_1\ list \rightarrow bool, \\ \sigma_{nil} &= \tau_2\ list, \\ \sigma_{hd} &= \tau_3\ list \rightarrow \tau_3, \\ \sigma_{tl} &= \tau_4\ list \rightarrow \tau_4\ list, \\ \sigma_{cons} &= (\tau_5 \times \tau_5\ list) \rightarrow \tau_5\ list. \end{align*}

The other identifiers (map, f, m) each occur more than once, and our rules demand that each occurrence is assigned the same type. The rules also demand that the following equations are satisfied for some types ρ1,ρ2,...\rho_1, \rho_2, ...,

σmap=σf×σm→ρ1,σnull=σm→bool,σhd=σm→ρ2,σtl=σm→ρ3,σf=ρ2→ρ4,σmap=σf×ρ3→ρ5,σcons=ρ4×ρ5→ρ6,ρ1=σnil=ρ6.\begin{align*} \sigma_{map} &= \sigma_f \times \sigma_m \rightarrow \rho_1, \\ \sigma_{null} &= \sigma_m \rightarrow bool, \\ \sigma_{hd} &= \sigma_m \rightarrow \rho_2, \\ \sigma_{tl} &= \sigma_m \rightarrow \rho_3, \\ \sigma_f &= \rho_2 \rightarrow \rho_4, \\ \sigma_{map} &= \sigma_f \times \rho_3 \rightarrow \rho_5, \\ \sigma_{cons} &= \rho_4 \times \rho_5 \rightarrow \rho_6, \\ \rho_1 &= \sigma_{nil} = \rho_6. \end{align*}

The first of these conditions relates the type of a function to those of its formal parameters; each of the other conditions arises from some subterm which is a function application, except the last, which is because a conditional expression has the same type as its two arms, and because the definiens and definiendum of a declaration have the same type.

Now these equations may be solved for the variables ρi,τi\rho_i, \tau_i, and σid\sigma_{id}; Morris [10] discusses the solution of such equations. Indeed, the situation is entirely appropriate for the use of the Unification Algorithm of Robinson [14]; our well-typing algorithm is based upon this algorithm, and (since in this case nothing more than unification is needed) we may conclude from Robinson's work that the most general type of map is obtained, i.e., any other type σmap\sigma_{map} which satisfies the equations must be a substitution instance of the type obtained. In fact, the solution of the above equations is

σmap=(γ→δ)×γ list→δ list,\sigma_{map} = (\gamma \rightarrow \delta) \times \gamma\ list \rightarrow \delta\ list,

where γ,δ\gamma, \delta are any distinct type variables. So this is the generic type of map, that is, to any occurrence of map within the scope of this declaration must be assigned some substitution instance of this type.

These instances need not be the same. Suppose that tok is a basic type (a token being a sequence of characters) and that we have available the identifiers (with their types) and tokl: tok list (a variable), length: tok \rightarrow int, sqroot: int \rightarrow real, two obvious functions.

Then in the expression map(sqroot, map(length, tokl)) the two occurrence of map will have types

((tok→int)×tok list)→int list,((int→real)×int list)→real list.\begin{align*} ((tok \rightarrow int) \times tok\ list) &\rightarrow int\ list, \\ ((int \rightarrow real) \times int\ list) &\rightarrow real\ list. \end{align*}

Similarly, if null, for example, had occurred twice in the definition of map, its types could have been different instances of

α list → bool\alpha \text{ list } \rightarrow \text{ bool}

but our rules demand that different occurrences of a formal parameter (ff, for example), or of an identifier (map) being recursively defined, shall have the same type.

In passing, note that the occurrences of map mentioned above can be regarded as uses of two separately declared (and monomorphic!) map functions, which differ only in that different types are explicitly provided for their arguments and results. As Gries and Gehani remark, the compiler could be given the task of generating these distinct declarations—or more accurately (since the programmer need not see the replication or even be aware of it), the task of generating different code for the body of the map function for use at distinct types. This would indeed be necessary in the above example if, for efficiency, token lists were implemented differently from integer lists (and the primitive polymorphic functions hd, tl etc., were correspondingly different). We are concerned with a conceptual framework in which these map functions may all be regarded semantically as the same object; then the implementor is left with the freedom to implement as few or many variants as he wishes.

It is clear from our example that the rules of typing need to be carefully framed. We leave this task until the next section, but here we look at one more example to illustrate what happens when let or letrec is used locally.

Example 2. Tagging. Suppose we want a function tagpair, such that tagpair (aa) is a function under which

(b,c)↦((a,b),(a,c)).(b, c) \mapsto ((a, b), (a, c)).

Of course, we can easily write

let tagpair(a)=λ(b,c)⋅((a,b),(a,c)).\text{let tagpair}(a) = \lambda(b, c) \cdot ((a, b), (a, c)).

Now we can explain, without setting up equations, how our well-typing algorithm tackles this declaration. It first assigns "unknown" types (i.e., type variables) α,β,\alpha, \beta, and γ\gamma to a,b,a, b, and cc. Then ((a,b),(a,c))((a, b), (a, c)) acquires type (α×β)×(α×γ)(\alpha \times \beta) \times (\alpha \times \gamma), and the λ\lambda-expression acquires β×γ→(α×β)×(α×γ)\beta \times \gamma \rightarrow (\alpha \times \beta) \times (\alpha \times \gamma); finally tagpair acquires

α→(β×γ→(α×β)×(α×γ))\alpha \rightarrow (\beta \times \gamma \rightarrow (\alpha \times \beta) \times (\alpha \times \gamma))

(∗)(*)

(no type equations have placed any constraint upon the types of a,b,a, b, and cc).

But consider another way of defining tagpair, using the (infixed) function

#:(α→β)×(γ→δ)→((α×γ)→(β×δ))\#: (\alpha \rightarrow \beta) \times (\gamma \rightarrow \delta) \rightarrow ((\alpha \times \gamma) \rightarrow (\beta \times \delta))

such that (f#g)(a,c)=(f(a),g(c))(f \# g)(a, c) = (f(a), g(c)), and the pairing function

pair: α→(β→α×β)\text{pair: } \alpha \rightarrow (\beta \rightarrow \alpha \times \beta)

such that pair(a)(b) = (a, b). We could write

let tagpair=λa⋅(let tag=pair(a) in tag # tag)let\ tagpair = \lambda a \cdot (let\ tag = pair(a)\ in\ tag\ \#\ tag)

We might then expect the well-typing algorithm to proceed as follows. First, α\alpha is assigned to aa. Then, using the generic type of pair, pair(a) acquires δ→α×δ\delta \to \alpha \times \delta. This is then used as the local generic type of tag, and the two occurrences of tag in tag #\# tag are assigned β→α1×β,γ→α2×γ\beta \to \alpha_1 \times \beta, \gamma \to \alpha_2 \times \gamma, respectively. The occurrence of #\# is assigned an instance of its generic type (again using new type variables) and the type equation for function application will cause the type

β×γ→(α1×β)×(α2×γ)\beta \times \gamma \to (\alpha_1 \times \beta) \times (\alpha_2 \times \gamma)

to be assigned to tag #\# tag, and to the body of the λ\lambda-expression, so that tagpair acquires the type

α→(β×γ→(α1×β)×(α2×γ)).\alpha \to (\beta \times \gamma \to (\alpha_1 \times \beta) \times (\alpha_2 \times \gamma)).

Now comparing () with (*), something has gone wrong; the second type is too general. The problem is that tag and its generic type depend upon the λ\lambda-bound variable aa and its type α\alpha, and we do not allow different bound occurrences of a λ\lambda-bound variable to have different type. Indeed, as far as type is concerned, we should get the same as if tagpair were defined in yet a third way, by

let tagpair=λa⋅(pair(a) # pair(a))let\ tagpair = \lambda a \cdot (pair(a)\ \#\ pair(a))

and the reader may be able to obtain (by setting up some equations as in Example 1) the expected type (*) in this case.

The solution is fortunately straightforward. We decree that in instantiating the type of a variable bound by let or by letrec. only those type variables which do not occur in the types of enclosing λ\lambda-bindings (or formal parameter bindings) may be instantiated. We call such instantiable variables (in a generic type) generic type variables.

Now in the second definition of tagpair, the locally defined tag acquired a generic type δ→α×δ\delta \to \alpha \times \delta, in which δ\delta is generic but α\alpha is not. Thus α\alpha should not have been instantiated in assigning types to the occurrences of tag, and then (**) would have been identical with (*). This example may appear a little contrived; indeed, our experience has been that almost always either all or none of the type variables of a generic type are generic. But there seem to be no simple syntactic constraints which would eliminate the exceptions, nor does it seem desirable to do so.

From the examples it becomes clear that the rules for typing variables bound, respectively, by let (or letrec) and by λ\lambda are going to be different. Thus, although our semantics for the two expressions

let x=e in e′;(λx⋅e′)elet\ x = e\ in\ e'; \qquad (\lambda x \cdot e')e

may be (and are) equivalent, it may be possible to assign types correctly to the former but not to the latter. An example is the pair

let I=λx⋅x in I(I);(λI⋅I(I))(λx⋅x).let\ I = \lambda x \cdot x\ in\ I(I); \qquad (\lambda I \cdot I(I))(\lambda x \cdot x).

A (partial) intuition for this is that a λ\lambda-abstraction may often occur without an argument; the second expression above contains a special (and rather unnecessary) use of abstraction, in that an explicit argument—(λx⋅x)(\lambda x \cdot x)—is present. Since the let construct (when translated) involves this restricted use of an abstraction, it is not unnatural that its rule for type assignment is less constrained. A compiler could, of course, treat all explicit occurrences of (λx⋅e′)e(\lambda x \cdot e')e in the less constrained manner.

The treatment of types in the interaction between λ\lambda-bindings (i.e., formal parameter bindings) and let bindings is really the core of our approach. It gives a consistent treatment of the nonglobal declaration of a procedure which may contain, as a free variable, a formal parameter of an enclosing procedure. This appears to be one of the more crucial difficulties with polymorphism, and therefore we feel justified in presenting our analysis in terms of a simple language (Exp) which excludes as much as possible that is irrelevant to the difficulty.

The reader may still feel that our rules are arbitrarily chosen and only partly supported by intuition. We certainly do not claim that the rules are the only possible ones, but the results given later demonstrate that they are semantically justified. In fact, we show that a program which admits a correct type assignment cannot fail at run-time due to a type error—or more precisely, that type constraints encoded in its semantics are always satisfied. It follows from this that compile-time type checking (i.e., the attempt to discover a correct type assignment) obviates the need to carry types at run-time, with an obvious gain in the efficiency of implementation.

This is of course a principal aim in compile-time type checking; another is the early detection of programming errors (many of which result in ill-typed programs). Our achievement is to extend type checking to embrace polymorphism. Moreover the type-checking algorithm in its final form (Algorithm JJ in Section 3) is remarkably simple, even though the proof of the Syntactic Soundness Theorem, which states that—if it succeeds—it produces a correct type assignment, is rather tedious.

We would like to give an independent characterization of the class of programs which can be well typed in our system, but we have no idea how to do this. However, we can give some pointers. At the suggestion of a referee we looked at Burge [1, Chapt. 3] concerning general functions for processing data structures. All of the functions there (with the exception of Section 3.11 which we did not examine) acquired the expected types from the ML type checker after they had been modified in two respects. First, Burge leaves implicit the coercion functions between a disjoint sum type and its summand types; we needed to make these explicit (this point was mentioned in our Introduction). Second, we used the ML abstract type construct (see Section 5 for an example) to formulate the recursive type definitions used by Burge. In this construct, the isomorphism between a defined abstract type and its representation is made explicit and must be used explicitly. To see the need for this requirement consider the case of an α\alpha-stream, which is defined to be a function which yields a pair consisting of an α\alpha and an α\alpha-stream. The type equation

α-stream=−−→(α×α-stream)\alpha\text{-stream} = -- \rightarrow (\alpha \times \alpha\text{-stream})

cannot be solved by unification (unless we allow infinite type expressions). But by treating the equation as an isomorphism, and using two functions to convert back and forth between an abstract type and its representation, this difficulty is removed. We claim that this solution is in keeping with the notion of abstract type (see [8], for example).

On the negative side, there are certainly useful expressions which we cannot well type, though we are not clear how burdensome it is to do without them. An obvious example is Curry's YY combinator.

Y=λf⋅(λx⋅f(x(x)))(λx⋅f(x(x)))Y = \lambda f \cdot (\lambda x \cdot f(x(x)))(\lambda x \cdot f(x(x)))

since self-application is ill typed for us. But letrec avoids the need for YY. More practically, consider

letF(f)=λ(a,b)⋅(f(a),f(b))let F(f) = \lambda(a, b) \cdot (f(a), f(b))

which—it may be argued—should accept as argument a function which is polymorphic enough to operate upon aa and bb of different type. For example,

F(reverse)(x,y)F(\text{reverse})(x, y)

produces a pair of reversed lists of different type if xx and yy are lists of different type. Our system rejects such a use of FF (since it requires aa and bb to have the same type), but admits

letreversepair=λ(x,y)⋅(reverse(x),reverse(y))let \text{reversepair} = \lambda(x, y) \cdot (\text{reverse}(x), \text{reverse}(y))

or any other specialization of the function argument of FF.

We feel that this example illustrates the main limitation of our system, but that we may have kept as much flexibility as is possible without the use of explicit type parameters. When these are introduced, the problem arises of the type of types; Reynolds [12] has made some progress in solving this problem, but we were anxious to see how much could be done while avoiding it.

3 A Simple Applicative Language and Its Types

3.1. The Language Exp

Let xx range over identifiers, that is

x∈Id.x \in \mathrm{Id}.

Then the expression language Exp is generated by the following grammar:

e::=x∣(ee′)∣if e then e′ else e′′∣e ::= x | (ee') | \text{if } e \text{ then } e' \text{ else } e'' |
λx⋅e∣fix x⋅e∣let x=e in e′.\lambda x \cdot e | \text{fix } x \cdot e | \text{let } x = e \text{ in } e'.

Here (ee′)(ee') means application, fix x⋅ex \cdot e stands for the least fixed point of λx⋅e\lambda x \cdot e, and the last clause binds xx to the value of ee throughout e′e'. We often use d,e,fd, e, f—with primes and suffixes—to range over Exp. Constants are omitted; we can imagine instead some standard bindings for certain identifiers in an initial environment.

We give an ordinary denotational semantics for Exp, in which we include a value "wrong," which corresponds to the detection of a failure at run-time. In this small language, the only failures are the occurrence of a non-Boolean value as the condition of a conditional, and the occurrence of a nonfunctional value as the operator of an application.

Our semantic domains may be taken to be complete partial orders (cpos); a cpo DD (see [9]) is a partially ordered set such that (a) there exist a minimum element, ⊥D\bot_D, (b) every directed subset of DD has a least upper bound in DD. Take as given a set {Bi}\{ B_i \} of basic domains, with B0=TB_0 = T, the three element truth value domain

Figure.

and we define recursively

V=B0+B1+⋯+F+W(disjoint sum of domains, with ⊥V adjoined as minimum element),V = B_0 + B_1 + \cdots + F + W \quad \text{(disjoint sum of domains, with } \bot_V \text{ adjoined as minimum element)},
F=V→V(continuous functions from V to V),F = V \rightarrow V \quad \text{(continuous functions from } V \text{ to } V),
W={⋅}(error).W = \{ \cdot \} \quad \text{(error)}.

The solution (up to isomorphism) of such a set of domain equations is assured by Scott [15]. Although he worked with complete lattices, the solution also exists in cpos (see Plotkin [11]).

The semantic function is E∈Exp→Env→V\mathcal{E} \in \text{Exp} \rightarrow \text{Env} \rightarrow V, where Env=Id→V\text{Env} = \text{Id} \rightarrow V, the domain of environments. We use η\eta to range over Env. In defining E\mathcal{E}, and later, we use some familiar notation of Scott and Strachey [16], illustrated by these examples (where DD is some summand of VV):

(i) If d∈Dd \in D, then dd in VV is the image of dd under the injection of DD into VV.

(ii) If v∈Vv \in V, then

vED={trueif v=d in V for some d∈D,=⊥Tif v=⊥V,=falseotherwise.v \mathbf{E} D = \begin{cases} \text{true} & \text{if } v = d \text{ in } V \text{ for some } d \in D, \\ = \bot_T & \text{if } v = \bot_V, \\ = \text{false} & \text{otherwise}. \end{cases}

(iii) If v∈Vv \in V, then

v∣D={dif v=d in V, for some d∈D,=⊥Dotherwise.v | D = \begin{cases} d & \text{if } v = d \text{ in } V, \text{ for some } d \in D, \\ = \bot_D & \text{otherwise}. \end{cases}

The environment η′=η{v/x}\eta' = \eta\{v/x\} is identical with η\eta except that η′(x)=v\eta'(x) = v. The value ⋅\cdot in VV (⋅∈W\cdot \in W) is written "wrong." We require the conditional function COND∈T→V→V→V\mathrm{COND} \in T \to V \to V \to V, where COND tvv′\mathrm{COND}\ tvv' is written t→v,v′t \to v, v' and takes the value

vif t=true,v′if t=false,⊥vif t=⊥T.\begin{array}{ll} v & \text{if } t = \text{true}, \\ v' & \text{if } t = \text{false}, \\ \bot_v & \text{if } t = \bot_T . \end{array}
3.2. Semantic Equations for Exp

In these equations, the open brackets [ ] indicate syntactic arguments.

E[x]η=η[x]E[(e1e2)]η=v1EF→(v2EW→wrong,(v1∣F)v2),wrongwhere vi is E[ei]η(i=1,2).E[if e1 then e2 else e3]η=v1EB0→(v1∣B0→v2,v3), wrongwhere vi is E[ei]η(i=1,2,3)E[λx⋅e]η=(λv⋅E[e]η{v/x}) in VE[fix x⋅e]η=Y(λv⋅E[e]η{v/x})E[let x=e1 in e2]η=v1EW→wrong,E[e2]η{v1/x}where v1=E[e1]η.\begin{align*} \mathcal{E}[x]\eta &= \eta[x] \\ \mathcal{E}[(e_1e_2)]\eta &= v_1 \mathbf{E} F \to (v_2 \mathbf{E} W \to \text{wrong}, (v_1 | F)v_2), \\ &\quad \text{wrong} \\ &\quad \text{where } v_i \text{ is } \mathcal{E}[e_i]\eta \quad (i = 1, 2). \\ \mathcal{E}[\text{if } e_1 \text{ then } e_2 \text{ else } e_3]\eta &= v_1 \mathbf{E} B_0 \to (v_1 | B_0 \to v_2 , v_3), \text{ wrong} \\ &\quad \text{where } v_i \text{ is } \mathcal{E}[e_i]\eta \quad (i = 1, 2, 3) \\ \mathcal{E}[\lambda x \cdot e]\eta &= (\lambda v \cdot \mathcal{E}[e] \eta\{v/x\}) \text{ in } V \\ \mathcal{E}[\text{fix } x \cdot e]\eta &= Y(\lambda v \cdot \mathcal{E}[e] \eta\{v/x\}) \\ \mathcal{E}[\text{let } x = e_1 \text{ in } e_2]\eta &= v_1 \mathbf{E} W \to \text{wrong}, \mathcal{E}[e_2] \eta\{v_1/x\} \\ &\quad \text{where } v_1 = \mathcal{E}[e_1]\eta. \end{align*}

Notes. (i) YY is the least fixed-point operation. In many languages the ee in fix f⋅e\text{fix } f \cdot e would be restricted to be an abstraction λy⋅e′\lambda y \cdot e', and then

let f=fix f⋅(λy⋅e′)let\ f = \text{fix } f \cdot (\lambda y \cdot e')

might receive the syntax

let rec f(y)=e′let\ rec\ f(y) = e'

(ii) It is easy to see that "let x=e1x = e_1 in e2e_2" has the same meaning under E\mathcal{E} as "(λx⋅e2)e1(\lambda x \cdot e_2)e_1". But part of our aim is a type discipline which admits certain expressions in the first form and yet rejects their translations into the second form; this is because λ\lambda-abstractions may in general occur without an explicit operand, and need more careful treatment.

(iii) The semantics for (e1e2)(e_1e_2) corresponds to call-by-value, since the test "v2EWv_2 \mathbf{E} W" ensures that the meaning of (e1e2)(e_1e_2) is ⊥v\bot_v if the meaning of e2e_2 is ⊥v\bot_v. The omission of this test gives a call-by-name semantics (a similar test may be omitted in the semantics of the let construct), and the Semantic Soundness Theorem goes through equally in this case.

3.3. Discussion of Types

We now proceed, in outline, as follows. We define a new class of expressions which we shall call types; then we say what is meant by a value possessing a type. Some values have many types, and some have no type at all. In fact "wrong" has no type. But if a functional value has a type, then as long as it is applied to the right kind (type) of argument it will produce the right kind (type) of result—which cannot be "wrong"!

Now we wish to be able to show that—roughly speaking—an Exp expression evaluates (in an appropriate environment) to a value which has a type, and so cannot be wrong. In fact, we can give a sufficient syntactic condition that an expression has this robust quality; the condition is just that the expression has a "well-typing" with respect to the environment, which means that we can assign types to it and all its subexpressions in a way which satisfies certain laws.

So there are two main tasks, once the laws of type assignment are given. The first—to show that an expression (or program) with a legal type assignment cannot "go wrong"—is tackled in this section; surprisingly enough, it is the easier task (at least for an applicative language). The second task is to discover a legal type assignment, given a program with incomplete type information. This task is often called type checking. Of course, this term can also mean just verifying that a given type assignment is legal; in a practical situation we probably require something between the two, since one cannot expect a programmer to attach a type to every subexpression. In Section 4 we look at the class of legal type assignments for a given program (the class is infinite in general, since we admit polymorphism), and we give an algorithm which, if it succeeds, produces a legal type assignment. We conjecture that if the latter exists then the algorithm finds one which is most general, in the sense that any other legal type assignment is a substitution instance of it (substituting types for type variables).

3.4. Types and their Semantics

The syntax of types is as follows.

(1) ι0,ι1,...\iota_0, \iota_1, ... are (basic) types; one for each BiB_i. (2) There is a denumerable set of type variables, which are types. We use α,β,γ,...\alpha, \beta, \gamma, ... to range over type variables. (3) If ρ\rho and σ\sigma are types, so is ρ→σ\rho \rightarrow \sigma.

A monotype is a type containing no type variables. We use μ,ν,π,...\mu, \nu, \pi, ... to range over monotypes. We use the word polytype when we wish to imply that a type may, or does, contain a variable.

We first give the semantics of monotypes; that is, we give the conditions under which a value v∈Vv \in V possesses a monotype μ\mu, which we write v:μv : \mu.

(i) v:ιiv : \iota_i iff v=⊥νv = \bot_\nu or vEBiv \mathbf{E} B_i (ii) v:μ→νv : \mu \rightarrow \nu iff either v=⊥νv = \bot_\nu, or vEFv \mathbf{E} F and (v∣F)u:ν(v | F)u : \nu whenever u:μu : \mu.

It is clear then that many values have no type. Examples are wrong, (λv∈V⋅wrong)(\lambda v \in V \cdot \text{wrong}) in VV, (λv∈V⋅vEB0→(v∣B0→x in V,y in V),wrong)(\lambda v \in V \cdot v \mathbf{E} B_0 \rightarrow (v | B_0 \rightarrow x \text{ in } V, y \text{ in } V), \text{wrong}) in VV (where x∈B1,y∈B2x \in B_1, y \in B_2 for example).

But in the last example if y∈B1y \in B_1 instead, then the function has type ι0→ι1\iota_0 \rightarrow \iota_1 (and no other).

Some values have many types; the identity function

(λv∈V⋅v)(\lambda v \in V \cdot v) in VV for example has type μ→μ\mu \rightarrow \mu for every μ\mu. And of course ⊥v\bot_v has every type (it is the only value which has every type).

This notion of type is derived from Scott [17]. In fact, it is what Scott calls functionality (after Curry), and is distinct from the notion of a retract. If we temporarily identify a type with the set of values which possess it, then it is easy to show that types are downward closed and directed complete, that is

(i) ∀v,v′∈V⋅(v:μ and v′⊑v)⇒v′:μ,\forall v, v' \in V \cdot (v : \mu \text{ and } v' \sqsubseteq v) \Rightarrow v' : \mu, (ii) For each directed subset XX of VV, (∀v∈X⋅v:μ)⇒⊔X:μ.(\forall v \in X \cdot v : \mu) \Rightarrow \sqcup X : \mu.

Retracts share the second property, but not the first. Recently Shamir and Wadge [18] have defined a type to be any set with these two properties, and they investigate the consequences of identifying a value vv with the type {v′∣v′⊑v}\{v' | v' \sqsubseteq v\}.

The semantics of polytypes is as follows. First, we use ρ≤σ\rho \leq \sigma to mean that ρ\rho may be obtained from σ\sigma by substituting types for type variables (≤\leq is clearly reflexive and transitive). For example,

μ→μ≤α→α≤β→β≤α→β,\mu \rightarrow \mu \leq \alpha \rightarrow \alpha \leq \beta \rightarrow \beta \leq \alpha \rightarrow \beta,

but

α→β≰β→β(unless α=β).\alpha \rightarrow \beta \not\leq \beta \rightarrow \beta \quad \text{(unless } \alpha = \beta).

Then we define

v:σ iff ∀μ≤σ⋅v:μ.v : \sigma \text{ iff } \forall \mu \leq \sigma \cdot v : \mu.

For example,

(λv⋅v)(\lambda v \cdot v) in V:α→α.V : \alpha \rightarrow \alpha.

Polytypes thereby also stand for subsets of VV, and these are also directed complete. The reader may like to think of each type variable in a polytype as universally quantified at the outermost; for example,

α→α "means" ∀α⋅α→α,\alpha \rightarrow \alpha \text{ "means" } \forall \alpha \cdot \alpha \rightarrow \alpha,

where the bound α\alpha ranges over monotypes. In fact, it is because α\alpha here ranges over monotypes (not all types) and because we do not admit expressions like

(∀α⋅α→α)→(∀α⋅α→α)(\forall \alpha \cdot \alpha \rightarrow \alpha) \rightarrow (\forall \alpha \cdot \alpha \rightarrow \alpha)

as types—though we can see they “mean” if the bound variables are taken to range over monotypes—that we avoid the difficulties (and also some of the interest) of Reynolds [12] in his richer notion of type.

We need the following simple properties, which are immediate from our definitions.

PROPOSITION 1. If v : σ and τ ≤ σ then v : τ.

PROPOSITION 2. If v : σ → τ and v' : σ, then (v | F)v' : τ.

In each case, a property of monotypes is lifted to polytypes.

3.5. Type Assignments

To prepare the ground for the theorem that well-typed expressions cannot “go wrong,” we need to define what is meant by typing an expression. We need first some notion of a type environment to give types to the free variables in an expression.

A prefix p is a finite sequence whose members have the form let x, fix x, or λx, where x is a variable. A prefixed expression (pe) has the form p | e, where every variable free in e occurs in a member of p. We separate the members of a prefix by a period (·).

Every pe has sub-pe’s given by the following, together with transitive reflexive closure:

(i) p | x has no sub-pe’s except itself, (ii) p | (ee') has sub-pe’s p | e and p | e', (iii) p | (if e then e' else e") has sub-pe’s p | e, p | e' and p | e", (iv) p | (λx · e) has sub-pe p · λx | e, (v) p | (fix x · e) has sub-pe p · fix x | e, (vi) p | (let x = e in e') has sub-pe’s p | e and p · let x | e'.

For example, λy | (let f = λx · (xy) in (fy)) has sub-pe’s (besides itself)

λy∣λx⋅(xy),λy⋅λx∣(xy),λy⋅λx∣x,λy⋅λx∣y,λy⋅letf∣(fy),λy⋅letf∣f,λy⋅letf∣y\lambda y | \lambda x · (xy), \quad \lambda y · \lambda x | (xy), \quad \lambda y · \lambda x | x, \quad \lambda y · \lambda x | y, \lambda y · let f | (fy), \quad \lambda y · let f | f, \quad \lambda y · let f | y

so a sub-pe is just a subexpression prefixed with all the variable bindings which enclose it. Note that λx · (xy) in the above is not enclosed by let f—it is not in the scope of this binding.

We say that a member let x or fix x or λx of p is active in p just if no x occurs to the right of it in p.

Now a typing of a pe p | e is an assignment of a type to each element of p, and to each subexpression and each λx, fix x, or let x in e, with the constraint that in a subexpression (let x = e' in e") the same type is assigned to let x and e'. Thus one typing of the illustrated pe (it is nearly, but not quite, a well typing in the sense later defined) is as follows:

λyα∣(letf(α→β)→β=(λxα→β(xα→βyα)β)(α→β)→βin(f(α→γ)→γyα)γ)γ.\lambda y_\alpha | (let f_{(\alpha→\beta)→\beta} = (\lambda x_{\alpha→\beta}(x_{\alpha→\beta}y_\alpha)_\beta)_{(\alpha→\beta)→\beta} in (f_{(\alpha→\gamma)→\gamma}y_\alpha)_\gamma)_\gamma .

We denote a typing of p | e by p̄ | ē, or by p̄ | ē_σ when we want to indicate the type σ assigned to e itself.

In any pˉ∣eˉ\bar{p} \mid \bar{e}, and any binding let xσx_{\sigma} in either pˉ\bar{p} or eˉ\bar{e}, a type variable in σ\sigma which does not occur in (the type of) any enclosing λyτ\lambda y_{\tau} or fix yτy_{\tau} binding is called a generic type variable for the binding let xσx_{\sigma}. In the example above, β\beta is generic, but α\alpha is not, for the binding let f(α→β)→βf_{(\alpha \rightarrow \beta) \rightarrow \beta}. Intuitively, the generic type variables in a binding let xσx_{\sigma} represent degrees of freedom for the types at which xx may be used; they represent the local polymorphism of xx. Notice that if no λ\lambda or fix bindings enclose let xσx_{\sigma}, then all the type variables in σ\sigma are generic. A generic instance of σ\sigma is an instance of σ\sigma in which only generic type variables are instantiated.

For technical reasons we require that generic type variables occur in a controlled manner. We say that pˉ∣dˉ\bar{p} \mid \bar{d} is standard if for every typed sub-pe pˉ′∣dˉ′\bar{p}' \mid \bar{d}' (with induced typing) the generic type variables of each member let xσx_{\sigma} of pˉ′\bar{p}' occur nowhere else in pˉ′∣dˉ′\bar{p}' \mid \bar{d}'. Thus in particular, if let xρ=eˉρx_{\rho} = \bar{e}_{\rho} in eˉσ′\bar{e}'_{\sigma} is a subexpression of dˉ\bar{d}, with induced typing, then the generic type variables in ρ\rho may not occur in eˉσ′\bar{e}'_{\sigma} (though they must of course occur in eˉρ\bar{e}_{\rho}).

We now define the notion of a well-typed (wt) pe as follows:

(i) pˉ∣xτ\bar{p} \mid x_{\tau} is wt iff it is standard, and either

(a) λxτ\lambda x_{\tau} or fix xτx_{\tau} is active in pˉ\bar{p}, or

(b) let xσx_{\sigma} is active in pˉ\bar{p}, and τ\tau is a generic instance of σ\sigma.

(ii) pˉ∣(eˉρeˉσ′)τ\bar{p} \mid (\bar{e}_{\rho}\bar{e}'_{\sigma})_{\tau} is wt iff pˉ∣eˉ\bar{p} \mid \bar{e} and pˉ∣eˉ′\bar{p} \mid \bar{e}' are both wt, and ρ=σ→τ\rho = \sigma \rightarrow \tau.

(iii) pˉ∣(ifeˉρ then eˉσ′ else eˉσ′′)τ′\bar{p} \mid (if \bar{e}_{\rho} \text{ then } \bar{e}'_{\sigma} \text{ else } \bar{e}''_{\sigma})_{\tau'} is wt iff pˉ∣eˉ\bar{p} \mid \bar{e}, pˉ∣eˉ′\bar{p} \mid \bar{e}' and pˉ∣eˉ′′\bar{p} \mid \bar{e}'' are all wt, ρ=ι0\rho = \iota_{0}, and σ=τ=τ′\sigma = \tau = \tau'.

(iv) pˉ∣(λxρ⋅eˉσ)τ\bar{p} \mid (\lambda x_{\rho} \cdot \bar{e}_{\sigma})_{\tau} is wt iff pˉ⋅λxρ∣eˉ\bar{p} \cdot \lambda x_{\rho} \mid \bar{e} is wt and τ=ρ→σ\tau = \rho \rightarrow \sigma.

(v) pˉ∣(fixxρ⋅eˉσ)τ\bar{p} \mid (fix x_{\rho} \cdot \bar{e}_{\sigma})_{\tau} is wt iff pˉ⋅fixxρ∣eˉ\bar{p} \cdot fix x_{\rho} \mid \bar{e} is wt and ρ=σ=τ\rho = \sigma = \tau.

(vi) pˉ∣(letxρ=eˉρ in eˉσ′)τ\bar{p} \mid (let x_{\rho} = \bar{e}_{\rho} \text{ in } \bar{e}'_{\sigma})_{\tau} is wt iff pˉ∣eˉ\bar{p} \mid \bar{e} and pˉ⋅letxρ∣eˉ′\bar{p} \cdot let x_{\rho} \mid \bar{e}' are both wt, and σ=τ\sigma = \tau.

Although this recursive definition is useful for some proofs, an alternative characterization of wt is sometimes useful. The proof of the next proposition is fairly straightforward, and we omit it. Note that a wt pˉ∣dˉ\bar{p} \mid \bar{d} is necessarily standard, by an easy structural induction.

Proposition 3. pˉ∣dˉ\bar{p} \mid \bar{d} is wt iff the following conditions hold:

(A) It is standard.

(B) For every (bound) occurrence xσx_{\sigma}, the corresponding binding occurrence is either λxσ\lambda x_{\sigma}, or fix xσx_{\sigma}, or let xτx_{\tau}, where σ\sigma is a generic instance of τ\tau.

(C) The following conditions hold for all subexpressions (with induced typing) of dˉ\bar{d}

(eˉρeˉσ′)τρ=σ→τ,(ifeˉρ then eˉσ′ else eˉτ′′′)τ′ρ=τ0 and σ=τ=τ′,(λxρ⋅eˉσ)ττ=ρ→σ,(fixxρ⋅eˉσ)τρ=σ=τ,(letxρ=eˉρ in eˉσ′)τσ=τ.\begin{array}{ll} (\bar{e}_{\rho}\bar{e}'_{\sigma})_{\tau} & \rho = \sigma \rightarrow \tau, \\ (if \bar{e}_{\rho} \text{ then } \bar{e}'_{\sigma} \text{ else } \bar{e}''_{\tau'})_{\tau'} & \rho = \tau_{0} \text{ and } \sigma = \tau = \tau', \\ (\lambda x_{\rho} \cdot \bar{e}_{\sigma})_{\tau} & \tau = \rho \rightarrow \sigma, \\ (fix x_{\rho} \cdot \bar{e}_{\sigma})_{\tau} & \rho = \sigma = \tau, \\ (let x_{\rho} = \bar{e}_{\rho} \text{ in } \bar{e}'_{\sigma})_{\tau} & \sigma = \tau. \end{array}

The typing which we illustrated above therefore fails to be wt for only one reason: The subexpression (f(α→γ)→γyα)γ(f_{(\alpha \to \gamma) \to \gamma} y_\alpha)_\gamma violates the first of conditions (C) in Proposition 3.

Consider another example. The following (with pˉ\bar{p} empty) is a well typing:

let Iα→α=(λxα⋅xα)α→α in(I(t→t)→(t→t)It→t)t→t.\begin{align*} let\ I_{\alpha \to \alpha} &= (\lambda x_\alpha \cdot x_\alpha)_{\alpha \to \alpha}\ in \\ &\quad (I_{(t \to t) \to (t \to t)} I_{t \to t})_{t \to t}. \end{align*}

Note that α\alpha is generic in the type α→α\alpha \to \alpha of the declaration of II, so may be instantiated (possibly differently) in the types of bound occurrences of II.

To illustrate the need to instantiate only generic type variables, for variables declared by let, notice first that in λxα⋅xβ\lambda x_\alpha \cdot x_\beta we must have α=β\alpha = \beta, by condition (B) of Proposition 3. Indeed, we can argue intuitively for this as follows: if we declare

let I=λx⋅x in ...let\ I = \lambda x \cdot x\ in\ ...

then we wish to have that any expression (Ie) in the scope of this declaration receives the same type as the subexpression e. But now suppose we write (with assigned types)

let Iα→β=(λxα⋅(let yα=xα in yβ)β)α→β in ...let\ I_{\alpha \to \beta} = (\lambda x_\alpha \cdot (let\ y_\alpha = x_\alpha\ in\ y_\beta)_{\beta})_{\alpha \to \beta}\ in\ ...

then—since this is semantically equivalent to the simpler declaration—we should again demand that α=β\alpha = \beta. But this is imposed in our definition of well typing, just because α\alpha is not generic for the binding let yαy_\alpha, so may not be instantiated in a bound occurrence of yy.

3.6. Substitutions

A substitution SS is a map from type variables to types. SS may be extended in a natural way to yield a map from types to types, from typed pe’s to typed pe’s, etc.

We say that SS involves a type variable α\alpha if either Sα≠αS\alpha \neq \alpha, or for some β≠α\beta \neq \alpha, α∈Sβ\alpha \in S\beta. (α∈τ\alpha \in \tau means α\alpha occurs in τ\tau.)

We need substitutions extensively in the second part of this paper, but for the present we need only one property relating substitutions and wt.

Proposition 4. If S involves no generic variables of a wt pˉ∣dˉ\bar{p} \mid \bar{d}, then S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}) is also a wt.

Proof. We use Proposition 3. First, observe that the assumption on SS yields that the generic variables for each binding in S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}) are exactly those for the corresponding binding in pˉ∣dˉ\bar{p} \mid \bar{d}. Since SβS\beta contains no generic variable when β\beta is not generic, S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}) is standard.

Second, if xσx_\sigma is bound by λxσ\lambda x_\sigma or fix xσx_\sigma in pˉ∣dˉ\bar{p} \mid \bar{d}, then xSσx_{S\sigma} is bound by λxSσ\lambda x_{S\sigma} or fix xSσx_{S\sigma} in S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}). If xσx_\sigma is bound by let xτx_\tau, and σ=[ρ1/α1,...,ρn/αn]τ\sigma = [\rho_1/\alpha_1, ..., \rho_n/\alpha_n]_\tau, where αi\alpha_i are the generic variables of τ\tau, then in S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}) xSσx_{S\sigma} is bound by let xSτx_{S\tau}, and Sσ=Sρ1/α1,...,Sρn/αnS\sigma = S\rho_1/\alpha_1, ..., S\rho_n/\alpha_n is a generic instance of SτS\tau.

Third, conditions (C) of Proposition 3 are easily verified for S(pˉ∣dˉ)S(\bar{p} \mid \bar{d}), using identities like S(σ→τ)=Sσ→SτS(\sigma \to \tau) = S\sigma \to S\tau.

3.7. Well-Typed Expressions Do Not Go Wrong

First we need a simple relation between semantic environments η\eta and our type environments—which are typed prefixed pˉ\bar{p}. We say

η respects pˉ iff, whenever let xρ or λxρ or fix xρ is active in pˉ,η[x]:ρ.\eta \text{ respects } \bar{p} \text{ iff, whenever let } x_\rho \text{ or } \lambda x_\rho \text{ or } \\ \text{fix } x_\rho \text{ is active in } \bar{p}, \eta[x] : \rho.

Theorem 1 (Semantic Soundness). If η\eta respects pˉ\bar{p} and pˉ∣dτ\bar{p} | d_\tau is well typed then E[d]η:τ\mathcal{E}[d]\eta : \tau.

Proof. A fairly simple structural induction. Take the six cases for dτd_\tau.

(i) xτx_\tau. Then either λxτ\lambda x_\tau or fix xτx_\tau is active in pˉ\bar{p}, and η[x]:τ\eta[x] : \tau, so E[x]η:τ\mathcal{E}[x]\eta : \tau, or let xσx_\sigma is active in pˉ\bar{p}, and η[x]:σ\eta[x] : \sigma; but then τ≤σ\tau \leq \sigma, so E[x]η=η[x]:τ\mathcal{E}[x]\eta = \eta[x] : \tau, by Proposition 1.

(ii) (eˉσ→τeˉσ)τ(\bar{e}_{\sigma \to \tau} \bar{e}_\sigma)_\tau. Then pˉ∣eˉσ→τ\bar{p} | \bar{e}_{\sigma \to \tau} is wt, so E[e]η:σ→τ\mathcal{E}[e]\eta : \sigma \to \tau, and similarly E[e′]η:σ\mathcal{E}[e']\eta : \sigma. Then from the semantic equation (remembering that wrong has no type) and by Proposition 2 we get E[d]η:τ\mathcal{E}[d]\eta : \tau.

(iii) (if eˉt0\bar{e}_{t_0} then eˉσ′\bar{e}'_{\sigma} else eˉσ′′\bar{e}''_{\sigma}). Straightforward; the only extra detail needed here is that ⊥ν\bot_\nu has every type.

(iv) (λxρ⋅eˉσ)ρ→σ(\lambda x_\rho \cdot \bar{e}_\sigma)_{\rho \to \sigma}. Then pˉ⋅λxρ∣eˉσ\bar{p} \cdot \lambda x_\rho | \bar{e}_\sigma is wt. Now we require (λv⋅E[e]η{v/x})(\lambda v \cdot \mathcal{E}[e] \eta\{v/x\}) in V:ρ→σV : \rho \to \sigma. Denote this function by ff in VV. The inverse of Proposition 2 does not hold, that is, to show ff in V:ρ→σV : \rho \to \sigma it is not sufficient (though it is necessary) that whenever v:ρ,fv:σv : \rho, fv : \sigma. What is required is that for every η→ν≤ρ→σ,f\eta \to \nu \leq \rho \to \sigma, f in V:μ→νV : \mu \to \nu.

Suppose then that μ→ν≤ρ→σ\mu \to \nu \leq \rho \to \sigma. Then there is a substitution SS, involving only the type variables in ρ\rho and σ\sigma, such that μ→ν=S(ρ→σ)\mu \to \nu = S(\rho \to \sigma). Then, since none of these type variables is generic in pˉ⋅λxρ∣eˉσ\bar{p} \cdot \lambda x_\rho | \bar{e}_\sigma, it follows that S(pˉ)⋅λxμ∣S(eˉ)νS(\bar{p}) \cdot \lambda x_\mu | S(\bar{e})_\nu is wt by Proposition 4. Moreover η\eta respects S(pˉ)S(\bar{p}) (since by Proposition 1 whenever η[x]:σ′\eta[x] : \sigma' and τ′≤σ′,η[x]:τ′\tau' \leq \sigma', \eta[x] : \tau') so for any v:μv : \mu we also have η{v/x}\eta\{v/x\} respects S(pˉ)⋅λxμS(\bar{p}) \cdot \lambda x_\mu.

It then follows by induction that E[e]η{v/x}:ν\mathcal{E}[e] \eta\{v/x\} : \nu, so we have shown that v:μv : \mu implies fv:νfv : \nu, and this yields ff in V:μ→νV : \mu \to \nu as required.

(v) (fixxρ⋅eˉρ)ρ(fix x_\rho \cdot \bar{e}_\rho)_\rho. Then pˉ⋅fixxρ∣eˉρ\bar{p} \cdot fix x_\rho | \bar{e}_\rho is wt. Now we require that v:ρv : \rho, where

v=Y(λv′⋅E[e]η{v′/x}).v = Y(\lambda v' \cdot \mathcal{E}[e] \eta\{v'/x\}).

Now v=⋃iviv = \bigcup_i v_i, where v0=⊥ν,vi+1=E[e]η{vi/x}v_0 = \bot_\nu, v_{i+1} = \mathcal{E}[e] \eta\{v_i/x\}, and by the directed completeness of types we only have to show vi:ρv_i : \rho for each ii.

Clearly v0:ρv_0 : \rho. Assume vi:ρv_i : \rho. Since η\eta respect pˉ\bar{p}, we have that η{vi/x}\eta\{v_i/x\} respects pˉ⋅fixxρ\bar{p} \cdot fix x_\rho, so by the main induction hypothesis vi+1:ρv_{i+1} : \rho also, and we are done.

(vi) (let x=eˉρx = \bar{e}_\rho in eˉσ′\bar{e}'_\sigma)_\sigma . Then pˉ∣eˉρ\bar{p} | \bar{e}_\rho is wt, so we immediately have v:ρv : \rho, where v=E[e]ηv = \mathcal{E}[e]\eta. We require E[e′]η{v/x}:σ\mathcal{E}[e'] \eta\{v/x\} : \sigma.

Now pˉ⋅letxρ∣eˉσ′\bar{p} \cdot let x_\rho | \bar{e}'_\sigma is also wt, and because v:ρv : \rho we have that η{σ/x}\eta\{\sigma/x\} respects pˉ⋅letxρ\bar{p} \cdot let x_\rho; the rest follows by the induction hypothesis.

As a corollary, under the conditions of the theorem we have

E[d]η≠wrong,\mathcal{E}[d]\eta \neq \text{wrong},

since wrong has no type.

4 A Well-Typing Algorithm and Its Correctness

4.1. The Algorithm W\mathcal{W}

In this section we tackle the question of finding a well typing for a prefixed expression. We present an algorithm W\mathcal{W} for this. We would like to prove that W\mathcal{W} is both syntactically sound and (in some sense) complete. By syntactic soundness, we mean that whenever W\mathcal{W} succeeds it produces a wt; by completeness, we mean that whenever a wt exists, W\mathcal{W} succeeds in finding one which is (in some sense) at least as general.

Although W\mathcal{W} is probably complete, it is difficult to find a simple proof. So we concentrate on soundness, and then comment on implementation of W\mathcal{W} and on extending it to deal with richer languages. Since a type-checking algorithm which simulates W\mathcal{W} has been working successfully for nearly 2 years in the context of the LCF metalanguage ML [2], we have evidence for its usefulness and even—to some extent—for its completeness.

W\mathcal{W} is based on the unification algorithm of Robinson [14]. Indeed, the only feature of well typing which does not fall directly within the framework of unification is the condition that τ\tau should be a generic instance of σ\sigma whenever xτx_{\tau} is bound by let xσx_{\sigma}. The completeness (in some sense) of W\mathcal{W} should follow from the second part of the following proposition concerning unification, but we need only the first half for our proof that W\mathcal{W} is sound.

Proposition 5 (Robinson). There is an algorithm U\mathcal{U}, taking a pair of expressions (over some alphabet of variables) and yielding a substitution, such that for any pair of expressions σ\sigma and τ\tau

(A) If U(σ,τ)\mathcal{U}(\sigma, \tau) succeeds, yielding UU, then UU unifies σ\sigma and τ\tau (i.e., Uσ=UτU\sigma = U\tau).

(B) If RR unifies σ\sigma and τ\tau, then U(σ,τ)\mathcal{U}(\sigma, \tau) succeeds yielding a UU such that for some substitution SS, R=SUR = SU.

Moreover, UU involves only variables in σ\sigma and τ\tau.

To find a well typing of a complete program ff, we would expect to supply also a typed prefix pˉ\bar{p}, containing only let bindings, giving the types of values bound to predefined identifiers. We would then expect W\mathcal{W} to yield f⃗\vec{f} such that pˉ∣f⃗\bar{p} \mid \vec{f} is a wt.

To state (and prove) W\mathcal{W} recursively however, prefixes containing all types of binding occur, and W\mathcal{W} in general needs to modify the nongeneric type variables in the prefix to meet constraints imposed on the program. We therefore make W\mathcal{W} return also a substitution TT, indicating the necessary transformation.

To be precise, we show that if W(pˉ,f)\mathcal{W}(\bar{p}, f) succeeds and returns (T,f⃗)(T, \vec{f}), then (Tpˉ)∣f⃗(T\bar{p})|\vec{f} is a wt.

We first state W\mathcal{W}. At certain points W\mathcal{W} requires type variables which have not previously occurred; such new type variables are denoted by β\beta or βi\beta_i. W(pˉ,f)\mathcal{W}(\bar{p}, f) is defined by induction on the structure of ff; the algorithm is expressed in a purely applicative programming style, in contrast with the more efficient algorithm J\mathcal{J} presented later, which is expressed more in the style of imperative programming.

Algorithm W\mathcal{W}

W(pˉ,f)=(T,f⃗), where\mathcal{W}(\bar{p}, f) = (T, \vec{f}), \text{ where}

(i) If ff is xx, then: if λxσ\lambda x_\sigma or fix xσfix\ x_\sigma is active in pˉ\bar{p} then T=I,f⃗=xσT = I, \vec{f} = x_\sigma; if let xσx_\sigma is active in pˉ\bar{p} then T=I,f⃗=xτT = I, \vec{f} = x_\tau where τ=[βi/αi]σ\tau = [\beta_i/\alpha_i]\sigma, αi\alpha_i are the generic variables of σ\sigma, and βi\beta_i are new variables.

(ii) If ff is (de)(de), then: let (R,d⃗ρ)=W(pˉ,d)(R, \vec{d}_\rho) = \mathcal{W}(\bar{p}, d), and (S,e⃗σ)=W(Rpˉ,e)(S, \vec{e}_\sigma) = \mathcal{W}(R\bar{p}, e); let U=U(Sρ,σ→β)U = \mathcal{U}(S\rho, \sigma \to \beta), β\beta new; then T=USRT = USR, and f⃗=U(((Sd⃗)e⃗)β)\vec{f} = U(((S\vec{d})\vec{e})_\beta).

(iii) If ff is (if d then e else e′)(if\ d\ then\ e\ else\ e'), then: let (R,d⃗ρ)=W(pˉ,d)(R, \vec{d}_\rho) = \mathcal{W}(\bar{p}, d) and U0=U(ρ,i0)U_0 = \mathcal{U}(\rho, i_0); let (S,e⃗σ)=W(U0Rpˉ,e)(S, \vec{e}_\sigma) = \mathcal{W}(U_0R\bar{p}, e), and (S′,e⃗σ′)=W(SU0Rpˉ,e′)(S', \vec{e}'_\sigma) = \mathcal{W}(SU_0R\bar{p}, e'); let U=U(S′σ,σ′)U = \mathcal{U}(S'\sigma, \sigma'); then T=US′SU0RT = US'SU_0R, and f⃗=U((if S′SU0d⃗ then S′e⃗ else e⃗′)σ)\vec{f} = U((if\ S'SU_0\vec{d}\ then\ S'\vec{e}\ else\ \vec{e}')_\sigma).

(iv) If ff is (λx⋅d)(\lambda x \cdot d), then: let (R,d⃗)=W(pˉ⋅λxβ,d)(R, \vec{d}) = \mathcal{W}(\bar{p} \cdot \lambda x_\beta , d), where β\beta is new; then T=RT = R, and f⃗=(λxRβ⋅d⃗ρ)Rβ→ρ\vec{f} = (\lambda x_{R\beta} \cdot \vec{d}_\rho)_{R\beta \to \rho}.

(v) If ff is (fix x⋅d)(fix\ x \cdot d), then: let (R,d⃗ρ)=W(pˉ⋅fix xβ,d)(R, \vec{d}_\rho) = \mathcal{W}(\bar{p} \cdot fix\ x_\beta , d), β\beta new; let U=U(Rβ,ρ)U = \mathcal{U}(R\beta, \rho); then T=URT = UR, and f⃗=(fix xURβ⋅Ud)URβ\vec{f} = (fix\ x_{UR\beta} \cdot Ud)_{UR\beta}.

(vi) If ff is (let x=d in e)(let\ x = d\ in\ e), then: let (R,d⃗ρ)=W(pˉ,d)(R, \vec{d}_\rho) = \mathcal{W}(\bar{p}, d); let (S,e⃗σ)=W(Rpˉ⋅let xρ,e)(S, \vec{e}_\sigma) = \mathcal{W}(R\bar{p} \cdot let\ x_\rho , e); then T=SRT = SR, and f⃗=(let xSρ=Sd⃗ in e⃗)σ\vec{f} = (let\ x_{S\rho} = S\vec{d}\ in\ \vec{e})_\sigma.

4.2. The Soundness of W\mathcal{W}

To show that W\mathcal{W} is sound it is convenient to have a few simple definitions. If AA is a type, a typed prefix, or a typed pe then

Vars⁡(A)=def{α∣α∈A,α a type variable}.\operatorname{Vars}(A) \overset{\mathrm{def}}{=} \{ \alpha \mid \alpha \in A, \alpha \text{ a type variable} \}.

If AA is a typed prefix or a typed pe then

Gen⁡(A)=def{α∣α∈A,α a generic type variable}.\operatorname{Gen}(A) \overset{\mathrm{def}}{=} \{ \alpha \mid \alpha \in A, \alpha \text{ a generic type variable} \}.
Spec⁡(A)=defVars⁡(A)−Gen⁡(A).\operatorname{Spec}(A) \overset{\mathrm{def}}{=} \operatorname{Vars}(A) - \operatorname{Gen}(A).

If SS is a substitution, then

Inv⁡(S)=def{α∣S involves α}\operatorname{Inv}(S) \overset{\mathrm{def}}{=} \{ \alpha \mid S \text{ involves } \alpha \}
={α∣∃β⋅Sβ≠β and α∈{β}∪Vars⁡(Sβ)}.= \{ \alpha \mid \exists \beta \cdot S\beta \neq \beta \text{ and } \alpha \in \{ \beta \} \cup \operatorname{Vars}(S\beta) \}.

We need the following simple properties, whose proof we omit:

Proposition 6. (A) Inv⁡(RS)⊆Inv⁡(R)∪Inv⁡(S)\operatorname{Inv}(RS) \subseteq \operatorname{Inv}(R) \cup \operatorname{Inv}(S) {#milner-1978-polymorphism-prop-6 .statement tag=0553} (B) Vars⁡(Sτ)⊆Vars⁡(τ)∪Inv⁡(S)\operatorname{Vars}(S\tau) \subseteq \operatorname{Vars}(\tau) \cup \operatorname{Inv}(S).

Theorem 2 (Syntactic Soundness). Let pˉ\bar{p} be a standard prefix, and p∣fp \mid f a (closed) pe. Then, if W(pˉ,f)=(T,fˉτ)\mathcal{W}(\bar{p}, f) = (T, \bar{f}_\tau),

(A) Tpˉ∣fˉT\bar{p} \mid \bar{f} is wt, (B) Inv⁡(T)⊆Spec⁡(pˉ)∪New\operatorname{Inv}(T) \subseteq \operatorname{Spec}(\bar{p}) \cup New, and (C) Vars⁡(τ)⊆Spec⁡(pˉ)∪New\operatorname{Vars}(\tau) \subseteq \operatorname{Spec}(\bar{p}) \cup New,

where New is the set of new type variables used by W\mathcal{W}.

Proof. By induction on the structure of ff, using the recursive definition of wt. We omit the cases of conditional and fix expressions, since nothing new arises there, and we treat the easier of the other cases first

(i) ff is xx. Then T=IT = I, so (B) is immediate. If λxσ\lambda x_\sigma or fix xσfix\ x_\sigma is active in pˉ\bar{p} then fˉ=xσ\bar{f} = x_\sigma and (A), (C) are immediate.

If let xσx_\sigma is active, then fˉ=xτ\bar{f} = x_\tau, where τ=[βi/αi]σ,{αi}\tau = [\beta_i/\alpha_i]\sigma, \{ \alpha_i \} generic in σ\sigma, New={βi}New = \{ \beta_i \}. Then Tpˉ∣fˉ=pˉ∣xτT\bar{p} \mid \bar{f} = \bar{p} \mid x_\tau is standard and (A), (C) follow easily.

(iv) ff is (λx⋅d)(\lambda x \cdot d). Let (R,dˉρ)=W(pˉ⋅λxβ,d)(R, \bar{d}_\rho) = \mathcal{W}(\bar{p} \cdot \lambda x_\beta , d), using new variables New1New_1, say.

By induction, R(pˉ⋅λxβ)∣d⃗ρR(\bar{p} \cdot \lambda x_\beta) | \vec{d}_\rho is wt, so for (A) f=Rpˉ∣(λxRβ⋅d⃗)Rβ→ρf = R\bar{p} | (\lambda x_{R\beta} \cdot \vec{d})_{R\beta \to \rho} is wt (defn of wt). Also by induction,

Inv⁡(R){⊆Spec⁡(pˉ⋅λxβ)∪New⁡1Vars⁡(ρ){=Spec⁡(pˉ)∪{β}∪New⁡1=Spec⁡(pˉ)∪New⁡(since New=New⁡1∪{β})\begin{align*} \operatorname{Inv}(R)\{ & \subseteq \operatorname{Spec}(\bar{p} \cdot \lambda x_\beta) \cup \operatorname{New}_1 \\ \operatorname{Vars}(\rho)\{ & = \operatorname{Spec}(\bar{p}) \cup \{\beta\} \cup \operatorname{New}_1 \\ & = \operatorname{Spec}(\bar{p}) \cup \operatorname{New} \quad \text{(since New} = \operatorname{New}_1 \cup \{\beta\}) \end{align*}

and (B) follows since T=RT = R. For (C)

Vars⁡(Rβ→ρ)⊆Inv⁡(R)∪{β}∪Vars⁡(ρ)by Proposition 6,\operatorname{Vars}(R\beta \to \rho) \subseteq \operatorname{Inv}(R) \cup \{\beta\} \cup \operatorname{Vars}(\rho) \quad \text{by Proposition 6,}
⊆Spec⁡(pˉ)∪New\subseteq \operatorname{Spec}(\bar{p}) \cup New

as required.

(vi) ff is (let x=dx = d in ee). Then let (R,d⃗ρ)=W(pˉ,d)(R, \vec{d}_\rho) = \mathcal{W}(\bar{p}, d), using new variables New1New_1, say. Then by induction

Rpˉ∣d⃗ is wt(1)R\bar{p} | \vec{d} \text{ is wt} \tag{1}
(1)
Inv⁡(R){⊆Spec⁡(pˉ)∪New⁡1(2)\operatorname{Inv}(R)\{ \subseteq \operatorname{Spec}(\bar{p}) \cup \operatorname{New}_1 \tag{2}
(2)

Now from (2)

Spec⁡(Rpˉ)⊆Inv⁡(R)∪Spec⁡(pˉ)\operatorname{Spec}(R\bar{p}) \subseteq \operatorname{Inv}(R) \cup \operatorname{Spec}(\bar{p})
⊆Spec⁡(pˉ)∪New⁡1(3)\subseteq \operatorname{Spec}(\bar{p}) \cup \operatorname{New}_1 \tag{3}
(3)

and from (1) by standardness

Gen⁡(Rpˉ∣d⃗)∩Spec⁡(Rpˉ)=∅.(4)\operatorname{Gen}(R\bar{p} | \vec{d}) \cap \operatorname{Spec}(R\bar{p}) = \varnothing. \tag{4}
(4)

We also have that Gen⁡(Rpˉ)=Gen⁡(pˉ)\operatorname{Gen}(R\bar{p}) = \operatorname{Gen}(\bar{p}), which is disjoint from Vars⁡(ρ)\operatorname{Vars}(\rho) by (2), hence Rpˉ⋅letxρR\bar{p} \cdot let x_\rho is a standard prefix.

So let S,eˉσ=W(Rpˉ⋅letxρ,e)S, \bar{e}_\sigma = \mathcal{W}(R\bar{p} \cdot let x_\rho, e) using new variables New2New_2. Then by induction

S(Rpˉ⋅letxρ)∣eˉ is wt(5)S(R\bar{p} \cdot let x_\rho) | \bar{e} \text{ is wt} \tag{5}
(5)
Inv⁡(S){⊆Spec⁡(Rpˉ⋅letxρ)∪New⁡2(6)\operatorname{Inv}(S)\{ \subseteq \operatorname{Spec}(R\bar{p} \cdot let x_\rho) \cup \operatorname{New}_2 \tag{6}
(6)

But Spec⁡(Rpˉ⋅letxρ)=Spec⁡(Rpˉ)\operatorname{Spec}(R\bar{p} \cdot let x_\rho) = \operatorname{Spec}(R\bar{p}), so putting (6) and (4) together yields (since New2New_2 are new variables)

Inv⁡(S)∩Gen⁡(Rpˉ∣d⃗)=∅\operatorname{Inv}(S) \cap \operatorname{Gen}(R\bar{p} | \vec{d}) = \varnothing

and it follows by Proposition 4 that S(Rpˉ∣d⃗)S(R\bar{p} | \vec{d}) is wt, and using (5) we have by definition of wt that

SRpˉ∣(letxSρ=Sd in eˉ)σSR\bar{p} | (let x_{S_\rho} = Sd \text{ in } \bar{e})_\sigma

is wt; but this is just Tpˉ∣f⃗T\bar{p} \mid \vec{f}, so we have proved (A). For (B), we have

Inv⁡(T)⊆Inv⁡(S)∪Inv⁡(R), by Proposition 6,⊆Spec⁡(pˉ)∪New1∪New2, using (6), (3), and (2)\begin{align*} \operatorname{Inv}(T) &\subseteq \operatorname{Inv}(S) \cup \operatorname{Inv}(R), \text{ by Proposition 6}, \\ &\subseteq \operatorname{Spec}(\bar{p}) \cup New_1 \cup New_2, \text{ using (6), (3), and (2)} \end{align*}

and for (C), by similar reasoning,

Vars⁡(σ)⊆Spec⁡(pˉ)∪New1∪New2.\operatorname{Vars}(\sigma) \subseteq \operatorname{Spec}(\bar{p}) \cup New_1 \cup New_2.

This is all we need, since New=New1∪New2New = New_1 \cup New_2 in this case.

(ii) ff is (dede). Let (R,d⃗o)=W(pˉ,d)(R, \vec{d}_o) = \mathcal{W}(\bar{p}, d), using new variables New1New_1, and (S,eˉo)=W(Rpˉ,e)(S, \bar{e}_o) = \mathcal{W}(R\bar{p}, e), using new variables New2New_2; then by similar reasoning to case (vi) we find that

SRpˉ∣S(d⃗o)is wtSRpˉ∣eˉois wtInv⁡(SR)}⊆Spec⁡(pˉ)∪New1∪New2.\begin{align*} SR\bar{p} \mid S(\vec{d}_o) &\quad \text{is wt} \tag{7} \\ SR\bar{p} \mid \bar{e}_o &\quad \text{is wt} \tag{8} \\ \operatorname{Inv}(SR)\} \subseteq \operatorname{Spec}(\bar{p}) \cup New_1 \cup New_2. \tag{9} \end{align*}
(7)

Now if U=U(Sρ,σ→β)U = \mathcal{U}(S\rho, \sigma \to \beta), where β\beta is new, we have by Proposition 5 that

USρ=Uσ→Uβ,Inv⁡(U)⊆Vars⁡(Sρ)∪Vars⁡(σ)∪{β}.\begin{align*} US\rho &= U\sigma \to U\beta, \tag{10} \\ \operatorname{Inv}(U) &\subseteq \operatorname{Vars}(S\rho) \cup \operatorname{Vars}(\sigma) \cup \{\beta\}. \tag{11} \end{align*}
(10)

It follows that UU involves no generic variables of the wt's (7) and (8), and that

USRpˉ∣U(((Sd⃗)eˉ)β)USR\bar{p} \mid U(((S\vec{d})\bar{e})_\beta)

is wt; but this is just Tpˉ∣f⃗T\bar{p} \mid \vec{f}, so we have proved (A). For (B), we have first that

New=New1∪New2∪{β},New = New_1 \cup New_2 \cup \{\beta\},

so

Inv⁡(T)⊆Inv⁡(U)∪Inv⁡(SR),by Proposition 6,⊆Spec⁡(pˉ)∪New,from (9) and (11),\begin{align*} \operatorname{Inv}(T) &\subseteq \operatorname{Inv}(U) \cup \operatorname{Inv}(SR), \quad \text{by Proposition 6}, \\ &\subseteq \operatorname{Spec}(\bar{p}) \cup New, \quad \text{from (9) and (11)}, \end{align*}

and for (C),

Vars⁡(τ)=Vars⁡(Uβ)⊆Inv⁡(U)∪{β},by Proposition 6,⊆Spec⁡(pˉ)∪New,again from (9) and (11).\begin{align*} \operatorname{Vars}(\tau) &= \operatorname{Vars}(U\beta) \\ &\subseteq \operatorname{Inv}(U) \cup \{\beta\}, \quad \text{by Proposition 6}, \\ &\subseteq \operatorname{Spec}(\bar{p}) \cup New, \quad \text{again from (9) and (11)}. \end{align*}
4.3. Implementation of W\mathcal{W}; a Simplified Algorithm J\mathcal{J}

As it stands, W\mathcal{W} is hardly an efficient algorithm; substitutions are applied too often. It was formulated to aid the proof of soundness. We now present a simpler algorithm J\mathcal{J} which simulates W\mathcal{W} in a precise sense.

I\mathcal{I} differs from W\mathcal{W} in two ways. First, we adopt an idea familiar in the literature on resolution-based theorem-proving systems, in which substitutions are composed, but only applied when it is essential to do so. Second, we take advantage of the fact that what is often needed in practice from a well-typing algorithm is not the whole type assignment ff, but only the type assigned to ff itself.

In fact I\mathcal{I} builds only one substitution, called EE, which is idempotent—that is, EE=EEE = E—which is to say that if β∈Eα\beta \in E\alpha, then Eβ=βE\beta = \beta. This substitution is held in a program variable (called EE) global to I\mathcal{I}, and I\mathcal{I} works by transforming EE. In place of the unification function U\mathcal{U}, I\mathcal{I} calls a unification procedure UNIFY which delivers no result but side-effects the variable EE. We assume that U\mathcal{U} and UNIFY are related as follows: of EE and E′E' are the values of EE before and after the command

UNIFY(σ,τ)\text{UNIFY}(\sigma, \tau)

and if

U(Eσ,Eτ)=U\mathcal{U}(E\sigma, E\tau) = U

then

E′=UE.E' = UE.

Thus, applying UNIFY to types σ\sigma and τ\tau in the presence of EE corresponds to applying U\mathcal{U} to types EσE\sigma and EτE\tau; σ\sigma and τ\tau may be thought of as implicit types standing for the explicit types which would be gained by applying the "explicating" substitution EE (the idempotency of EE means that further explication is unnecessary). The effect of UNIFY (if successful) is to generate the new explicating substitution E′E'. I\mathcal{I} will similarly handle an implicit typed prefix pˉ\bar{p}, which can be explicated when necessary by applying EE. We assume that I\mathcal{I} has local variables ρ,σ,\rho, \sigma, and σ′\sigma', whose values are implicit types, and generates its result in a fourth variable τ\tau.

Assuming an initial idempotent EE, I\mathcal{I} is given a typed prefix pˉ\bar{p} and an expression ff such that EpˉE\bar{p} is standard and p∣fp \mid f is a pe (i.e., all free identifiers in ff are bound in pp). Here is the algorithm:

Algorithm I\mathcal{I}

I(pˉ,f)=τ, where\mathcal{I}(\bar{p}, f) = \tau, \text{ where}

(i) If ff is xx, then:

If λxσ\lambda x_\sigma or fix xσx_\sigma is active in pˉ\bar{p}, τ:=σ\tau := \sigma. If let xσx_\sigma is active in pˉ\bar{p}, τ:=[βi/αi]Eσ\tau := [\beta_i/\alpha_i]E\sigma, where αi\alpha_i are the generic type variables of let xEσx_{E\sigma} in EpˉE\bar{p}, and βi\beta_i are new variables.

(ii) If ff is (de) then:

ρ:=I(pˉ,d);σ:=I(pˉ,e);\rho := \mathcal{I}(\bar{p}, d); \quad \sigma := \mathcal{I}(\bar{p}, e);

UNIFY (ρ,σ→β\rho, \sigma \rightarrow \beta); (β\beta new)

τ:=β\tau := \beta

(iii) If ff is (if d then e else e', then:

ρ:=J(pˉ,d);UNIFY (ρ,ι0);\rho := \mathcal{J}(\bar{p}, d); \text{UNIFY } (\rho, \iota_0);
σ:=J(pˉ,e);σ′:=J(pˉ,e′);\sigma := \mathcal{J}(\bar{p}, e); \sigma' := \mathcal{J}(\bar{p}, e');
UNIFY(σ,σ′);τ:=σ\text{UNIFY}(\sigma, \sigma'); \tau := \sigma

(iv) If ff is (λx⋅d)(\lambda x \cdot d), then:

ρ:=J(pˉ⋅λxβ,d);(β new)\rho := \mathcal{J}(\bar{p} \cdot \lambda x_\beta , d); (\beta \text{ new})
τ:=β→ρ\tau := \beta \rightarrow \rho

(v) If ff is (fix x \cdot d), then:

ρ:=J(pˉ⋅fixxβ,d);(β new)\rho := \mathcal{J}(\bar{p} \cdot fix x_\beta , d); (\beta \text{ new})
UNIFY(β,ρ);τ:=β\text{UNIFY}(\beta, \rho); \tau := \beta

(vi) If ff is (let x = d in e) then:

ρ:=J(pˉ,d);σ:=J(pˉ⋅let xρ,e);\rho := \mathcal{J}(\bar{p}, d); \sigma := \mathcal{J}(\bar{p} \cdot \text{let } x_\rho , e);
τ:=σ.\tau := \sigma.

What is the simulation relation between J\mathcal{J} and W\mathcal{W}? It is simply expressed by the following proposition, whose proof we omit, since it is an easy structural induction involving few of the subtleties which we encountered in the soundness theorem.

PROPOSITION 7. Let p | f be a pe, E be idempotent, and Ep̄ be standard. Then J(pˉ,f)\mathcal{J}(\bar{p}, f) succeeds (producing τ′\tau' and a new value E′E' for EE) iff W(Epˉ,f)\mathcal{W}(Ep̄, f) succeeds (producing T and fτ′f_{\tau'}), and moreover if both succeed

(A) E′=TE,E' = TE, (B) E′τ′=τ.E'\tau' = \tau.

Thus the type produced by J\mathcal{J}, when explicated, is exactly that ascribed to ff by W\mathcal{W}. In practice, EE may be efficiently represented by a table INST of variable-type pairs, representing those variables which have hitherto been instantiated. The effect of UNIFY is merely to add some entries to INST, representing instantiation of previously uninstantiated variables. The substitution EE represented by INST is given recursively as follows

E(i)=i(basic types),E(i) = i \quad \text{(basic types)},
E(α)=E(ρ)if (α,ρ)∈INST for some ρ,E(\alpha) = E(\rho) \quad \text{if } (\alpha, \rho) \in \text{INST for some } \rho,
=αotherwise,= \alpha \quad \text{otherwise},
E(σ→τ)=Eσ→Eτ.E(\sigma \rightarrow \tau) = E\sigma \rightarrow E\tau.

In fact, in the extended version of J\mathcal{J} implemented for ML, which is written in LISP, INST itself is represented by a property (in the LISP sense) INSTANCE of type variables. Each type variable α\alpha has ρ\rho as its INSTANCE property value, if (α,ρ)∈INST(\alpha, \rho) \in \text{INST} for some ρ\rho; otherwise the property value is NIL.

5 Types in Extended Languages

We now consider some extensions of our language, and how our results may be strengthened to apply to them.

(1) As we said in the introduction, the addition of extra (primitive) type operators such as ×\times (Cartesian product), ++ (disjoint sum) and list (list forming), causes no difficulty. Together with →\rightarrow, these are the primitive type operators in the language ML. For ×\times one has the standard polymorphic functions pair: α→β→(α×β)\alpha \rightarrow \beta \rightarrow (\alpha \times \beta) (one could add the syntax (e,e′)(e, e') for pair (e)(e′)(e)(e')), fst: α×β→α\alpha \times \beta \rightarrow \alpha, snd: α×β→β\alpha \times \beta \rightarrow \beta.

For ++, one has inl: α→α+β\alpha \rightarrow \alpha + \beta, outl: α+β→α\alpha + \beta \rightarrow \alpha, isl: α+β→bool\alpha + \beta \rightarrow bool, inr: β→α+β\beta \rightarrow \alpha + \beta (left and right injections), outr: α+β→β\alpha + \beta \rightarrow \beta (left and right projections), isr: α+β→bool\alpha + \beta \rightarrow bool (left and right discriminators) with natural interpretations. For list, one has the standard list-processing functions mentioned in Section 2. Notice that all members of a list must have the same type.

With appropriate adjustment to the semantic domains, the Semantic Soundness Theorem extends naturally; the Syntactic Soundness Theorem goes through virtually without change.

(2) Next, we consider assignable variables and assignments. One way (used in ML) of adding these is to allow the assignment expression form "x:=ex := e" (whose value is the value of ee), and the expression form "letref x=e in e′letref\ x = e\ in\ e'" to declare an assignable variable, initialized to the value of ee. The first effect of these additions is a major change in the semantic domains, since now expressions may have side effects. Although we believe that a Semantic Soundness Theorem may be proved, it appears to be a cumbersome task. The reason for the difficulty is illustrated by considering

λx⋅(y:=x)\lambda x \cdot (y := x)

which is an identity function with a side effect. To say that it has type α→α\alpha \rightarrow \alpha, meaning that whenever it is given an argument of type μ\mu it gives a result of type μ\mu, takes no account of the side effect on yy. What is required is a more careful definition (in terms of the semantic domains) of what such functional types mean, which takes side effects into account.

By contrast, it is easy enough to give well-typing rules for the two new kinds of expression. We would extend the definition of wt by the following clauses:

(i) (c) If letref xτx_{\tau} is active in a standard prefix pˉ\bar{p}, then pˉ∣xτ\bar{p} \mid x_{\tau} is wt. (Thus, all letref-bound occurrences of xx must have the same type).

(vii) pˉ∣(xρ:=eˉσ)τ\bar{p} | (x_\rho := \bar{e}_\sigma)_\tau is wt iff pˉ∣eˉ\bar{p} | \bar{e} is wt, and letref xρx_\rho is active in pˉ\bar{p}, and ρ=σ=τ\rho = \sigma = \tau. (viii) pˉ∣(letref xρ=eˉρ in eˉσ′)τ\bar{p} | (letref\ x_\rho = \bar{e}_\rho\ in\ \bar{e}'_\sigma)_\tau is wt iff pˉ∣eˉ\bar{p} | \bar{e} and pˉ⋅letref xρ∣eˉ′\bar{p} \cdot letref\ x_\rho | \bar{e}' are wt, and σ=τ\sigma = \tau.

It is a routine matter to extend the algorithm W\mathcal{W} and the Syntactic Soundness Theorem to handle these clauses. But returning again to semantic considerations, there is a problem concerned with nonlocal assignments within λ\lambda-abstractions (procedures). Consider

let g=(letref y=nil in λz⋅y:=cons(z,y))let\ g = (letref\ y = nil\ in\ \lambda z \cdot y := cons(z, y))

which sets up yy as a hidden (own) variable for the function gg. With our rules, gg obtains generic type α→α list\alpha \to \alpha\ list. Remembering that the value of an assignment is the value assigned, each call of gg augments the hidden yy, returning the augmented list as value. Now since α\alpha is generic, the expressions

g(2),g(true)g(2), \qquad g(true)

are both admissible within the scope of gg, with types int list and bool list, respectively. But if they are evaluated in this order, the value of the second expression is a list whose two members are true and 2; it is not a list of Boolean values!

There are at least two possible solutions to this dilemma. One is to decree that in such a situation α\alpha is not a generic type variable. Another, which we adopt in ML, is to forbid nonlocal assignments to polymorphic assignable variables within λ\lambda-abstractions (procedures). Polymorphic assignable variables are still useful, for example, in programming iterations (while statements) which themselves can be given simple wt rules.

I believe that the second solution, even slightly relaxed, admits a Semantic Soundness Theorem. The details, however, are unattractive, and I have been discouraged (particularly after a useful discussion with John Reynolds) from attempting to complete the proof. What is rather needed is a language design which pays more respect to side effects; one approach might be to modify PASCAL by requiring that all variables assigned in a procedure be listed as output parameters of the procedure. But how to combine this with the rather useful properties of own variables is, as far as I know, an open problem in language design, and a good solution would be a valuable step forward. For a recent promising attempt to control side effects see Reynolds [13].

(3) To complete the list of nontrivial extensions which we have included in ML, consider the declaration (possibly recursive) of a new type operator in terms of old ones. Such a declaration may have nonglobal scope. If it is also accompanied by the declaration of a set of functions over the new type operator, and the explicit definition of the type operator is only available in defining the set of functions (not within the whole scope of the new type operator), one has a version of what is currently called abstract type. In ML, we would define the class of binary trees whose tips are labeled by objects of arbitrary type as follows, using the type variable α\alpha to stand for the type of tip labels:

absrectype α bitree=α+(α bitree×α bitree)with sons(t)=⋯and maketree(t,t′)=⋯and tiptree(a)=⋯absrectype\ \alpha\ bitree = \alpha + (\alpha\ bitree \times \alpha\ bitree) \\ with\ sons(t) = \cdots \\ and\ maketree(t, t') = \cdots \\ and\ tiptree(a) = \cdots

in which only the omitted defining expressions are given access to the representation of bitrees. The defined functions are polymorphic, with generic types

α bitree→(α bitree)2,(α bitree)2→α bitree,α→α bitree.\alpha \text{ bitree} \rightarrow (\alpha \text{ bitree})^2,\quad (\alpha \text{ bitree})^2 \rightarrow \alpha \text{ bitree},\quad \alpha \rightarrow \alpha \text{ bitree}.

For full details of this construct, see [2]; we owe the construct partly to Lockwood Morris and to discussion with Jerry Schwarz. In this case the wt rules are also rather easy; we have not checked syntactic and semantic soundness, but suspect that there should be no great difficulty.

(4) Two features which contribute a kind of polymorphism have been completely ignored so far. The first is coercions. The expression x:=42x := 42, where xx is a real assignable variable, is ill typed for us. However, there is no barrier to having the type checker report such instances of ill typing and allowing the compiler to receive the report and insert a coercion to rectify it.

The second feature is to allow certain procedures—either standard or user defined—to possess more than one type. We may wish “+” to possess int2→intint^2 \rightarrow int and real2→realreal^2 \rightarrow real, without of course possessing α2→α\alpha^2 \rightarrow \alpha (which is the least general polytype having the two given types as instances). While we have not investigated the question, there appears to be a good possibility of superposing this feature upon our discipline.

6 Conclusion

We have presented a discipline of polymorphic programming which appears to be practically useful, and have given a rather simple type-checking algorithm. In a restricted language we have shown that this algorithm can be proved correct (the proof was factored into two Soundness Theorems). Though much work remains to be done, we hope to have made the point that the practice of type checking can and should by supported by semantic theory and proof.

Acknowledgments

I am indebted to the referees for their apposite comments on the first version of this paper, which drew my attention to some inaccuracies and also led me to a clearer exposition in several places. My thanks also go to Dorothy McKie for her careful preparation of two versions of the paper.

References

  1. W. H. Burge, "Recursive Programming Techniques," Addison-Wesley, Reading, Mass., 1975.
  2. M. Gordon, R. Milner, and C. Wadsworth, "Edinburgh LCF," CSR-11-77, Computer Science Dept., Edinburgh University, 1977.
  3. M. Gordon, R. Milner, L. Morris, M. Newey, and C. Wadsworth, A metalanguage for interactive proof in LCF, in "Proc. 5th Annual ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages, Tucson, Arizona, 1978."
  4. D. Gries and N. Gehani, Some ideas on data type in high-level languages, Comm. ACM. 20 (1977), 414–420.
  5. R. Hindley, The principal type-scheme of an object in combinatory logic, Trans. Amer. Math. Soc. 146 (1969), 29–60.
  6. B. W. Lampson, J. J. Horning, R. L. London, J. G. Mitchell, and G. L. Popek, Report on the programming language Euclid, SIGPLAN Notices (ACM) 12, 2 (1977).
  7. P. J. Landin, The next 700 programming languages, Comm. ACM 9 (1966), 157–164.
  8. B. H. Liskov and S. Zilles, Programming with abstract data types, in “Proc. of ACM SIGPLAN conference on Very High Level Languages,” SIGPLAN Notices (ACM) 9 (1974), 50–59.
  9. R. Milner, “Models of LCF,” Mathematical Centre, Amsterdam, Tracts, Vol. 82, pp. 49–63, 1976.
  10. J. H. Morris, “Lambda-Calculus Models of Programming Languages,” Ph.D. Thesis, MAC-TR-57, MIT, 1968.
  11. G. Plotkin, A power-domain construction, SIAM J. Comput. 5 (1976), 452–487.
  12. J. C. Reynolds, “Towards a Theory of Type Structure,” Systems and Inform. Sci., Syracuse University, 1974.
  13. J. C. Reynolds, Syntactic control of interference, in “Proc. 5th ACM Symposium on Principles of Programming Languages, Tucson, Arizona, 1978,” pp. 39–46.
  14. J. A. Robinson, A machine-oriented logic based on the resolution principle, J. Assoc. Comput. Mach. 12 (1965), 23–41.
  15. D. Scott, Lattice theoretic models for various type-free calculi, in “Proc. 4th International Congress for Logic, Methodology and Philosophy of Science, Bucharest, Rumania, 1972.”
  16. D. Scott and C. Strachey, Towards a mathematical semantics for computer languages, in “Proc. Symposium on Computers and Automata,” Vol. 21, Microwave Res. Inst. Symposia Series, Polytech. Inst. of Brooklyn, 1971.
  17. D. Scott, Data types as lattices, SIAM J. Comput. 5 (1976), 522–587.
  18. A. Shamir and W. Wadge, Data types as objects, in “Proc. 4th ICALP Conference, Turku, Finland, 1977.”
  19. C. Strachey, Fundamental concepts in programming languages, Notes for the International Summer School in Computer Programming, Copenhagen, 1967.
  20. R. D. Tennent, On a new approach to representation-independent data classes, Acta Inform. 8 (1977), 315–324.
  21. B. Wegbreit, The treatment of types in EL1, Comm. ACM 17 (1974), 251–264.
  22. A. van Wijngaarden et al., Revised report on the algorithmic language ALGOL 68, Acta Informatica 5 (1975), 1–236.
  23. R. A. Wulf, R. L. London, and M. Shaw, “Abstraction and Verification in ALPHARD: Introduction to Language and Methodology,” ISI/RR-76-46, Univ. of California, 1976.

Read off the page by a vision model reading the page images using olmOCR-2-7B-1025-FP8.