Proc. 18th Symp. Principles of Programming Languages, 1991, pages 291–302. Polymorphic type inference and assignment

Xavier Leroy∗ Pierre Weis∗ Ecole Normale Sup´erieure INRIA Rocquencourt

Abstract that physical modification of data compromises type safety, since it can invalidate static typing assumptions. We present a new approach to the polymorphic typing As demonstrated here, the use of polymorphic mu- of data accepting in-place modification in ML-like lan- table data (that is, data structures that can be mod- guages. This approach is based on restrictions over type ified in place) must be restricted. An obvious way to generalization, and a refined typing of functions. The tackle this problem, used in early implementations of type system given here leads to a better integration of ML [3], is to require all such data to have monomor- imperative programming style with the purely applica- phic, statically-known types. This restriction trivially tive kernel of ML. In particular, generic functions that solves the problem, but it also makes it impossible to allocate mutable data can safely be given fully polymor- write polymorphic functions that create mutable values. phic types. We show the soundness of this type system, This fact has unfortunate consequences. and give a type reconstruction algorithm. A first drawback is that it is not possible to provide generic, efficient implementations of most data struc- tures (vectors, hash tables, graphs, B-trees, ... ), as 1 Introduction they require physical modification. Even a trivial func- Polymorphic type disciplines originate in the study of λ- tion such as taking a vector of an arbitrary type and calculus and its connections to constructive logic [7, 14], returning a copy of it is not well-typed with the policy so it is no surprise it fits very nicely within purely above, since it creates a vector with a statically un- applicative languages, without side effects. However, known type. polymorphism becomes problematic when we move to- Another drawback is that polymorphic mutable val- ward conventional imperative languages (Algol, Pas- ues are prohibited even if they are not returned, but cal), and allow physical modification of data structures. used for internal computation only. As a consequence, The problem appeared at an early stage of the design of most generic functions cannot be written in an impera- ML [8, p. 52], when assignment operators were provided tive style, with references holding intermediate results. for the primitive data types of references and vectors. Consider the familiar map functional: 1 Consider the following example, in ML : let rec applicative map f l = let r = ref [] in if null l then [] else r := [1]; f (head l) :: applicative map f (tail l) if head(!r) then ... else ... Here is an alternate implementation of map in impera- If we naively give type ∀α. α list ref to the reference r, tive style: we can first use it with type int list ref , and store in it let imperative map f l = the list with 1 as single element — an int list, indeed. let argument = ref l and result = ref [] in Given its type, we can also consider r as having type while not (null !argument) do bool list ref , hence head(!r) has type bool, and the if result := f (head !argument) :: !result; statement is well-typed. However, head(!r) evaluates argument := tail !argument to 1, which is not a valid boolean. This example shows done; ∗Authors’ address: B.P.105, 78153 Le Chesnay, France. reverse !result E-mail: [email protected], [email protected]. 1Survival kit for the reader unfamiliar with ML: [ ] is the (poly- Some ML type systems reject imperative map as ill- morphic) empty list, [a1; ... ; an] the list with elements a1 . . . an. typed. Others give it a less general type than its ref x allocates a new reference (indirection cell), initialized to x. r := x updates the contents of reference r by x.!r returns the purely applicative version. In any case, the imperative current contents of reference r. version cannot be substituted for the applicative one,

Page 1 even though they have exactly the same semantics. As let binding.) Hence, what we restrict is type general- demonstrated here, the programming style (imperative ization: we never generalize a type variable that may be vs. applicative) interferes with the type specifications. free in the type of a reference. Such a type variable is This clearly goes against modular programming. said to be dangerous. Not generalizing dangerous type Some enhancements to the ML type system have been variables ensures that a mutable value always possesses proposed [4, 16, 17, 1], that weaken the restrictions over a monotype. polymorphic mutable data. Standard ML [11] incorpo- It remains to detect dangerous type variables at rates some of these ideas. These enhanced type systems generalization-time. Though this suggests a complex make it possible to define many useful generic functions and expensive static analysis, this turns out not to be over mutable data structures, such as the function that the case: dangerous variables can be determined by copies a vector. However, these systems are still not mere examination of the type being generalized, as we powerful enough: they fail to infer the most general shall now illustrate. type for the imperative map example above; and they do not work well in conjunction with higher-order func- 2.1 Datatypes tions. Because of these shortcomings, Standard ML does not provide adequate support for the imperative Consider the example given in introduction: programming style. This is a major weakness for a general-purpose programming language. let r = ref [] in r := [1]; In this paper, we present a new way to typecheck mu- if head(!r) then ... else ... table data within a polymorphic type discipline. Our type system is a simple extension of the one of ML. The expression ref [ ] has type A = α list ref . This It permits type reconstruction and possesses the prin- type is a ref type, and α is free in it, hence α is dan- cipal type property. It requires minimal modifications gerous in A. The let construct does not generalize α, to the ML type algebra. Yet it definitely improves the hence α gets instantiated to int when typing r := [1], support for imperative programming in ML. It allows and typing if head(!r) ... leads to a type clash. generic functions over mutable data structures to have References may be embedded into more complex data fully polymorphic types. It is powerful enough to assign structures (pairs, lists, concrete datatypes). Fortu- to a function written in imperative style the same type nately, the type of a data structure contains enough as its purely applicative counterpart. For example, in information to retrieve the types of the components of this system, the imperative implementation of map can- the structure. For instance, a pair with type A × B not be distinguished from the usual, applicative one, as contains one value of type A and one value of type B. far as types are concerned. Therefore, any variable which is dangerous in A or in B is also dangerous in A × B. For instance, in The remainder of the paper is organized as follows. In section 2, we introduce informally our type system, let r = ([ ], ref [ ]) in e and show the need for a more precise typechecking of functions. Section 3 formalizes the ideas above. We the expression ([ ], ref [ ]) has type α list × β list ref , state the typechecking rules, show their soundness, and where β is dangerous but not α. Hence in e, variable r give a type reconstruction algorithm. Section 4 briefly has type ∀α. α list × β list ref . compares our approach with previous ones. We give a The treatment of user-defined datatypes is similar. few concluding remarks in section 5. Parameterless datatypes cannot contain polymorphic data, so there is no dangerous variable in them. Pa- rameterized datatypes come in two flavors: those which 2 Informal presentation introduce new ref types (such as type α foo = A | B of α ref ), and those which don’t. The former are In this section, we informally introduce our typing dis- treated like ref types: all variables free in their parame- cipline for mutable data, focusing on references to be ter(s) are considered dangerous. The latter are treated more specific. Unlike the Standard ML approach, we like product types: their dangerous variables are the do not attempt to detect the creation of polymorphic dangerous variables of their parameter(s). references. What we prohibit is the use of a reference with two different types. The only way to use a value 2.2 Functions with several types is to generalize its type first, that is, to universally quantify over some of its type variables. Function types are treated specially. The reason is that (In ML, the only construct that generalizes types is the a value with type A → B does not contain a value of

Page 2 type A, nor a value of type B, in contrast with regular tion may possess free variables. Suppose that a function datatypes such as A × B or A list. Hence it seems f has a free variable r which is bound to a reference out- there are no dangerous variables in type A → B, even side the function. Function f can access and update the if A or B contain dangerous variables themselves. For reference, yet the type of r does not necessarily appear instance, the function in the type of f. A classic example is the functional presentation of references as a pair of functions, one for let make ref = function x → ref x access, the other for update: has type α → α ref , and α is not dangerous in it, so it is fully polymorphic. It is actually harmless by let functional ref x = itself. What’s harmful is to apply make ref to a poly- let r = ref x in morphic argument such as [ ], bind the result with a (function () → !r), let construct, and use it with two different types. But (function z → r := z) this is not possible in the proposed type system, since The expression functional ref [ ] has type (unit → make ref [ ] has type β list ref , and this type will not be α list) × (α list → unit), where no type variable is generalized, as β is dangerous in it. In our approach, dangerous, so it is fully polymorphic, yet it breaks type generic functions that create and return mutable ob- safety just as ref [ ] does. The problem is that the types jects are given very general types. Type safety is en- of the free variables of a function do not appear in the sured by controlling what can be done with the result type of the function. In this context, it is useful to think of their application, as described above. of functions as closures. Closures, in contrast with any The analysis above is based solely on the type of other data structure, are not adequately described by the function result. Hence, the usage of a polymor- their (functional) type: we do not know anything about phic function is unrestricted if the function does not the types of the values contained in the environment return any references, as witnessed by the absence of part of a closure. ref types in its codomain type. This holds even if the Our solution is to keep track of what is inside a clo- function allocates references with statically unknown sure. We associate with any function type a set of types, types for internal purposes, but does not return them. the types of all variables free in the function. We could For instance, this is the case for the imperative map put this set of types as a third argument to the arrow functional given in the introduction: it is given type type constructor. For technical reasons, we find it more ∀α, β. (α → β) → α list → β list, the very same type convenient to add an extra level of indirection in record- as its purely applicative counterpart applicative map. ing this set of types. Therefore, each function type is The imperative map functional can be substituted for adorned with a label. Labels are written L, M, and applicative map in any context. In particular, it can be L applied to highly polymorphic arguments: the expres- labeled function types are written A → B. Separately, sion we record constraints on those labels. Constraints have the format A.L, meaning that objects of type A are imperative map (function x → x)[] allowed to occur in environments of type L. An envi- ronment of type L is not required to contain a value of is well-typed, and returns the fully polymorphic empty type A. It is not allowed to contain a value of a type B list, even though two references were created with type unless B.L is one of the recorded constraints. α list ref . The type system guarantees that these ref- This way, function type labels allow typings of erences are used consistently inside the function, and the environment parts of closures. For instance, are not exported outside. functional ref now has type: Another strength of this type-based analysis is its good handling of higher-order functions and partial L M N α → (unit → α) × (α → unit) applications. Consider the partial application of applicative map to make ref : our type system correctly with α ref . M, α ref .N recognize it as harmless, and gives it the fully polymor- phic type ∀α. α list → α ref list. This is not the case hence functional ref [ ] has type: for ML type systems that attempt to control the cre- M N ation of references (see section 4). (unit → β list) × (β list → unit) with β list ref . M, β list ref .N 2.3 Functions with free variables thus revealing the presence of a polymorphic reference The careful reader may have noticed a flaw in the dis- in each function of the pair, and preventing the gen- cussion above: we have neglected the fact that a func- eralization of β. The rule is that in a functional type

Page 3 A →M B, a variable α is dangerous iff there exists a without free variables, assuming there is no constraint constraint C.M with α dangerous in C. over L in the current environment. Instead, the right This typing of closures gives a precise account of typing is ∀L. (int →L int) →M int. In more complex sit- the interleaving of internal computation and parameter uations, the current environment contains constraints passing in (curried) functions, and of the possible data over the label to be generalized. These constraints are sharing between invocations. For instance, it succeeds discharged in the type schema, and reintroduced at spe- in distinguishing the following two functions: cialization time. Type schemas therefore have the for- mat ∀V1 ...Vn.A with Γ, where the Vi are either labels function () → ref []: or type variables, and Γ is a sequence of constraints. unit →L α list ref let r = ref [] in function () → r : 3 Formalization unit →L α list ref with α list ref . L In this section, we formalize a calculus based on the The former is harmless, since it returns a fresh refer- ideas above. ence each time, so it can be generalized. The latter always return the same reference, which is therefore shared between all calls to the function. It must re- 3.1 Syntax main monomorphic, which is indeed the case, since α is The language we consider is the core ML language, λ- a dangerous variable in its type. calculus plus a distinguished let construct. We shall Before presenting the type system formally, we now assume a built-in int type, with integer constants. The give the main intuitions behind the typing of closures. store is presented through the type A ref of references Constraints are synthesized during the typing of ab- to a term of type A, and the operations ref(a), to al- M locate a new reference to term a,!a to get the contents stractions, as follows: when giving type A → B to the of the reference a, and a := b to update the content of abstraction e = function x → ..., for each variable a by b. y free in e, we look up the type Cy of y in the typing We assume given a countable set Var of term vari- environment and record the constraint C .M. y ables, with typical elements x, y. In the following, i It is always safe to add new constraints, since the ranges over integers. The syntax of terms, with typical constraints over a label L are intended to give an upper elements a, b, is as follows: bound for what can go inside closures of type L. (This may lead to less general types, however, since more vari- a ::= x | i | λx. a | (b a) | let x = a in b | ables will be declared dangerous.) Unifying two labeled ref(a) | !a | a := b function types A →L B and C →M D is easy: it suffices to identify both labels L and M, resulting in a single 3.2 Typechecking label which bears the previous constraints on M as well L Type expressions, with typical elements A, B, have as those on L. For instance, assuming f : int → int, the following syntax: the expression A ::= X | int | A →L B | A ref if ... then f else let z = 1 in function x → x + z In the definition above, X stands for a type variable, ranging over a given countable set TVar. Function has type int →L int as well, with the additional con- types A →L B are annotated by a label L, taken from a straint that int .L. countable set Lbl. Labels are distinct from term vari- Finally, to give the most general type to function- ables and type variables. als, we must be able to generalize over labels, in the Type schemas, with typical element Σ, are composed same way as we generalize over regular type variables. of type expressions with some type variables and some Consider the functional: labels universally quantified. They also contain a se- quence of constraints Γ. Constraints have the format function f → 2 + (f 1) Σ .L.

It must be possible to apply it to any function mapping integers to integers, whatever its closure may contain. Σ ::= A | ∀V1 ...Vn.A with Γ Yet if we give it the type (int →L int) →M int without Γ ::= ² | Σ . L, Γ generalizing over L, we could only apply it to functions V ::= X | L

Page 4 (int) E ` i : int with Γ

E(x) = ∀X1 ...Xn,L1 ...Lm.A with Γ (varspec) 0 E ` x : A{Xi ← Bi,Lj ← Mj} with Γ{Xi ← Bi,Lj ← Mj} ∪ Γ E[x ← A] ` b : B with Γ for all y free in λx. b,(E(y) .L) ∈ Γ (fun) E ` λx. b : A →L B with Γ E ` b : A →L B with Γ E ` a : A with Γ (app) E ` (b a): B with Γ E ` a : A with Γ (Σ, Γ0) = Gen(A, E, Γ) E[x ← Σ] ` b : B with Γ0 ∪ Γ00 (letgen) E ` let x = a in b : B with Γ0 ∪ Γ00 E ` a : A with Γ E ` a : A ref with Γ (ref) (deref) E ` ref(a): A ref with Γ E ` !a : A with Γ E ` a : A ref with Γ E ` b : A with Γ (assign) E ` a := b : A with Γ

Figure 1: The typing rules.

¡ 0 ¢ Given a type A in the context of a constraint se- DV (∀V1 ...Vn.A with Γ ) with Γ = quence Γ, we define its free variables (labels as well as 0 DV (A with Γ ∪ Γ ) \{V1 ...Vn} type variables) FV (A with Γ) and its dangerous free FV (E with Γ) = variables DV (A with Γ) as follows. The intuition be- [ hind the definition of FV is that the components of a FV (E(x) with Γ) functional type A →L B are not only A, B and L, but x∈Dom(E) also any type expression C such that C.L is one of the The typing rules are given in figure 1. They are very recorded constraints. similar to the rules for ML, except for the additional FV (X with Γ) = {X} handling of constraints, reminiscent of the treatment of subtyping hypotheses in type inference systems with FV (int with Γ) = ∅ subtypes [12, 6]. FV (A ref with Γ) = FV (A with Γ) The rules define the proposition “term a has type FV (A →L B with Γ) = {L} ∪ FV (A with Γ) ∪ A under assumptions E and constraints Γ”, written FV (B with Γ) ∪ E ` a : A with Γ. The typing environment E is a [ partial mapping from term variables to type schemas. FV (Σ with Γ) We write E[x ← A] for the environment identical to E, (Σ.L)∈Γ except that x is mapped to A. We assume the usual set DV (X with Γ) = ∅ operations are defined over constraints Γ in the obvious DV (int with Γ) = ∅ way. DV (A ref with Γ) = FV (A with Γ) As in Standard ML [11, p. 21], the Gen operator L [ used in the (letgen) rule is responsible for generaliz- DV (A → B with Γ) = DV (Σ with Γ) ing as many type variables as possible in a type. Here, (Σ.L)∈Γ we also generalize over labels whenever possible. In ad- To complete the definition above, we extend FV and dition, we prohibit generalization over dangerous type DV to type schemas and to typing environments in the variables. A tentative definition would therefore be obvious way: Gen(A, E, Γ) = ∀V1 ...Vn.A where the set {V1 ...Vn} is FV (A with Γ) \ DV (A with Γ) \ FV (E with Γ). ¡ 0 ¢ FV (∀V1 ...Vn.A with Γ ) with Γ = However, it would be incorrect to generalize over a vari- 0 FV (A with Γ ∪ Γ ) \{V1 ...Vn} able which remains free in the constraint sequence used

Page 5 e e e i[s] =⇒ int(i)[s] x[s] =⇒ e(x)[s](λx. a)[s] =⇒ clos(x, a, e|FV (a))[s]

e e e1[x←v2] b[s] =⇒ clos(x, c, e1)[s1] a[s1] =⇒ v2[s2] c[s2] =⇒ v3[s3] e (b a)[s] =⇒ v3[s3]

e e[x←v1] e 0 0 a[s] =⇒ v1[s1] b[s1] =⇒ v2[s2] a[s] =⇒ v[s ] `∈ / Dom(s ) e e 0 (let x = a in b)[s] =⇒ v2[s2] ref(a)[s] =⇒ loc(`)[s [` ← v]] e 0 e e a[s] =⇒ loc(`)[s ] a[s] =⇒ loc(`)[s1] b[s1] =⇒ v[s2]

e 0 0 e !a[s] =⇒ s (`)[s ] (a := b)[s] =⇒ v[s2[` ← v]]

Figure 2: The evaluation rules later. Therefore, Gen also discharges in the schema all 3.4 Soundness of typing “generic” constraints, i.e. the constraints in Γ where The type system presented here is sensible with respect one of the Vi is free, and returns the remaining con- straints, to be used further in the typing derivation. to the evaluation mechanism above: no well-typed term can evaluate to wrong. Definition 1 (Generalization) Let A be a type ex- pression, E be a typing environment, Γ be a constraint Proposition 1 Let a be a term, A be a type, Γ be a sequence. Define: sequence of constraints such that we can derive ∅ ` a : A with Γ. Then, for all stores s0, a[s0] does not evalu- {V1 ...Vn} = ∅ ate to wrong; that is, we cannot derive a[s0] =⇒ wrong. FV (A with Γ) \ DV (A with Γ) \ FV (E with Γ).

Let Γ0 be the sequence of those constraints Σ .L in Γ The proof can be found in appendix. It closely fol- lows Tofte’s [16, chapter 5]. The crucial point is that such that L is one of the Vi. Then: closure labels and constraints give a better control over 0 0 Gen(A, E, Γ) = (∀V1 ...Vn.A with Γ ), (Γ \ Γ ) the types of store locations than in ML. As a conse- quence, a variable cannot be free in the type of a loca- 3.3 Evaluation tion reachable from a value without being dangerous in the type of that value. This guarantees the soundness We give here an evaluation mechanism for terms of our of type generalization. calculus, using structural operational semantics. The evaluation relation a[s] =⇒e v[s0] maps a term a, in the context of an evaluation environment e and a store s, 3.5 Type reconstruction 0 to some value v, and a modified store s . Values have In this section, we consider an adaptation to our lan- the following syntax: guage of the well-known Damas-Milner type reconstruc- v ::= int(i) | clos(x, a, e) | loc(`) tion algorithm for ML (algorithm W of [5]). In the fol- lowing, we write mgu(A, B) for the principal unifier of Here, ` ranges over a countable set Loc of locations. types A and B, if A and B are unifiable. (Otherwise, Stores are partial mappings from locations to values. the type inference algorithm fails.) Type expressions Since we assume call-by-value, evaluation environments are terms of a two-sorted free algebra, hence this en- are partial mappings from term variables to values. The sures the existence of a principal unifier, that can be rules given in figure 2 define precisely the evaluation re- obtained by the classical unification algorithm between lation, assuming standard left-to-right evaluation order. terms of a free algebra. To account for run-time type errors, we introduce Let a be a term, E be a typing environment, and the special result wrong, and state that if none of the Γ be an initial sequence of constraints. We define evaluation rules match, then a[s] =⇒e wrong. This infer(E, a, Γ) as the triple (A, σ, ∆), where A is a type way, we can distinguish between type errors and non- expression, σ a substitution and ∆ a constraint se- termination. quence, as follows:

Page 6 infer(E, i, Γ) = 3.6 Relation to ML (int, Id, Γ) We have introduced closure typing as a way to keep track of mutable values embedded in functions. As infer(E, x, Γ) = a consequence, two expressions having the same func- let (∀X ...X ,L ...L .A with ∆) = E(x) 1 n 1 m tional type in ML may now be distinguished by their let Y ,...,Y be fresh type variables 1 n closure type, and we may fear that this leads to a type (not free in E nor in A) system more restrictive than the one of ML. Ideally, we and M ,...,M be fresh labels 1 m would like the purely applicative fragment of our cal- let σ = {X ← Y ,L ← M } i i j j culus (that is, without the ref type constructor, and in (σA, Id, Γ ∪ σ∆) the ref, := and ! term constructors) to be a conser- vative extension of ML: any pure, closed term that is infer(E, λx. b, Γ) = well-typed in ML should also be well-typed in our cal- let X,L be fresh variables culus. Unfortunately, this is not the case — but for let Θ = {E(y) .L | y free in λx. b} more subtle reasons than the one outlined above. let B, σ, ∆ = infer(E[x ← X], b, Γ ∪ Θ) L Actually, two type expressions in our system cannot in (σX → B, σ, ∆) be distinguished by their closure labels only. The rea- son is that unification cannot fail because of the labels: infer(E, (b a), Γ) = given the syntax of type expressions, a label can only be let B, ρ, Θ = infer(E, b, Γ) matched against another label, and labels are treated as let A, σ, ∆ = infer(ρE, a, Θ) variables as far as unification is concerned. Therefore, let X,L be fresh variables in our system, unification between types is conservative L let µ = mgu(σB, A → X) with respect to unification between the corresponding in (µX, µσρ, µ∆) ML types. To be more precise, we introduce the “strip” operator ↓, that maps type expressions in our calculus infer(E, let x = a in b, Γ) = to ML type expressions, by erasing the labels from func- let A, σ, ∆ = infer(E, a, Γ) tion types: let Σ, ∆0 = Gen(A, σE, ∆) let B, ρ, Θ = infer((σE)[x ← Σ], b, ∆0) int↓ = int X↓ = X (A ref )↓ = A↓ ref in (B, ρσ, Θ) (A →M B)↓ = A↓ → B↓ infer(E, ref(a), Γ) = Then, two type expressions A and B are unifiable if let A, σ, ∆ = infer(E, a, Γ) and only if A↓ and B↓ are, and in this case, taking σ = in (A ref , σ, ∆) mgu(A, B) and ρ = mgu(A↓,B↓), we have (σC)↓ = ρ(C↓) for all types C. infer(E, !a, Γ) = This lemma, along with the close resemblance be- let A, σ, ∆ = infer(E, a, Γ) tween our algorithm infer and Damas-Milner’s W al- let X be a fresh variable gorithm, lead us to believe that, given the same pure let µ = mgu(A, X ref ) term, both algorithms infer the same type, modulo clo- in (µX, µσ, µ∆) sure labels. However, this does not hold because of the generalization step in the case of a let construct. infer(E, a := b, Γ) = Consider the typing of let x = a in b. Assume that, let A, σ, ∆ = infer(E, a, Γ) starting from typing environment E, algorithm infer let B, ρ, Θ = infer(σE, b, ∆) infers type A with Γ for a, while algorithm W , start- let µ = mgu(ρA, B ref ) ing from the corresponding ML typing environment E↓, in (µB, µρσ, µΘ) infers the corresponding ML type A↓. We must check that both algorithms generalize exactly the same type variables, so that they will type b in compatible envi- ronments. This could be not the case for two reasons. The first reason is that algorithm infer does not gen- This algorithm enjoys the good properties of the eralize dangerous type variables, while W does. But Damas-Milner algorithm: it is correct and complete this is no problem here, since we consider only the pure with respect to the typing rules, and the inferred type fragment of our calculus, without the ref type construc- is the most general one. The proof is very similar to tor, hence the set of dangerous variables of any type is Damas’ proof [4]. always empty.

Page 7 The second, more serious reason is that closure typ- simplify sequences of constraints. Here are some possi- ing introduces additional free variables in a given type. ble simplification rules: In general, FTV (A with Γ), the set of free type vari- ables in A with Γ, is a superset of FV (A↓). (Take for Σ .M, Σ .M → Σ .M instance A = int →L int and Γ = X.L.) So it is not Σ .M → ² if Σ is closed obvious that FTV (A with Γ) \ FTV (E with Γ), the A × B.M → A.M,B.M set of type variables generalized by infer, is the same as FV (A↓) \ FV (E↓), the set of type variables general- These three simplifications are obviously sound with ized by W . Indeed, there are cases where infer does not respect to the computation of free and dangerous vari- generalize some type variable X, while W does, because ables. The third rule actually generate more con- X is free in E with Γ, but not in E↓. Consider: straints, but this may open the way to further simplifi- cations, e.g. if A = B, or if A is closed. λz. let id¡ = ¢ In addition, constraint handling becomes quite cheap λx. if ... then z else (λy. x; y) ; x if we distribute constraints inside types, instead of han- in id id dling a single list of constraints. We suggest grouping together all constraints over the same label L, arranged Assuming x : X and y : Y , the term (λy. x; y) is given as a list of type schemes. This list represents the label M type Y → Y with X.M, and the if construct forces L itself. To identify two labels, we just have to con- z to have the same type. Therefore, when we attempt catenate the two lists, and this can be done in constant to generalize X →L X (the type of λx. . . . ; x), we have time, using e.g. difference lists. Hence, when unifying E(z) = Y →M Y under constraints Γ = X.M,Y →M two type expressions A and B, the additional work of Y.L, and we cannot generalize over X, since it is free identifying labels takes time at most proportional to the in the type of z. Hence, id remains monomorphic, and size of A, B. Therefore, unification can be performed in the application id id is ill-typed. In ML, we would linear time, as in ML. have z : Y → Y , so we could freely generalize over X, getting id : ∀X.X → X, and the whole term would be well-typed. 4 Comparison with other type The example above is quite convoluted, and it is the systems simplest one we know that exhibits this “variable cap- ture through labels” phenomenon. We need more expe- In this section, we compare our type system with pre- rience with the proposed type system to find whether vious proposals of polymorphic type systems for lan- this phenomenon happens in more practical situations. guages featuring physical modification. However, in case this turns out to be a serious flaw of our closure typing system, we are investigating two possible improvements that seem to avoid variable cap- 4.1 Standard ML tures. We consider first the systems proposed for ML. All these One direction is to record less constraints when typ- systems rely on detecting the creation of mutable val- ing a λ-abstraction. In the example above, one could ues (e.g. by special typechecking rules for the ref con- argue that the constraint X.M should not be recorded, struct), and ensuring that the resulting mutable values on the grounds that x is “not actually used” in the body have monomorphic types. of the function, as witnessed by the fact that the type The first system we consider is the one proposed by variable X is not free in the type of the function result, Tofte [16, 17], and adopted in Standard ML [11]. It nor in the type of the parameter. makes use of weak type variables (written with a ∗ su- The other direction is to check that two function perscript) to prohibit polymorphic references. Weak types are compatible without actually identifying their type variables cannot be generalized by a let binding, closure types. This way, we could avoid the propagation unless the corresponding expression is guaranteed to be of the constraint X.M to the type of z. non-expansive, i.e. that it does not create any refer- ences. Damas [4] proposed a related, but slightly dif- 3.7 Pragmatics of constraint handling ferent scheme, that gives similar results in most cases. The other system is an extension of Tofte’s, used in Practically, the main concern with the type inference the Standard ML of New Jersey implementation [1]. In algorithm above is the additional overhead introduced this scheme, type variables are no longer partitioned by the handling of constraints. First, it is possible to into weak and non-weak variables; instead, each type

Page 8 Standard ML SML-NJ Our system make ref α∗ → α∗ ref α1 → α1 ref α →L α ref make ref [] rejected rejected rejected imperative map (α∗ → β∗) → (α2 → β2) → (α →L β) →M α list →N ∗ ∗ 2 2 α list → β list α list → β list β list with α →L β . N

imperative map id [] rejected rejected α list applicative map make ref rejected rejected α list →L α ref list id make ref rejected rejected α →L α ref (raise Exit : α ref ) α ref α ref rejected

Figure 3: Comparison with other type systems variable has an associated integer, its “degree of weak- apply the identity function id to the make ref function. ness”, or “strength”. This degree measures the number Our type system gives the same type to id make ref of abstractions that have to be applied before the cor- and to make ref . The others don’t, and we take this as responding reference is actually created. Regular, un- strong evidence that they do not handle full function- constrained type variables have strength infinity. Vari- ality correctly. ables with strength zero cannot be generalized. Vari- The last example illustrates a weakness of our type- ables with strength n > 0 can be generalized, but each based approach. By using exceptions, for instance, one function application decrements the strength of vari- may mimic the creation of a reference as far as types ables. are concerned, without actually creating one. In the The comparative results are given in figure 3. For expression (raise Exit : α ref ), our type system con- each test program, we give its most general type in each siders that α is dangerous and does not generalize it. system. We assume these are top-level phrases. As in Other type systems recognize that no references are cre- most ML implementations, we reject top-level phrases ated, hence they correctly generalize it. This flaw of our whose types cannot be closed (because some free vari- method has little impact on actual programming, how- ables cannot be generalized). ever. The first test is the make ref function, defined as function x → ref x. It exercises the possibility of writing generic functions that create and return up- datable data structures. Most functions over vectors, matrixes, doubly linked lists, ... are typed similarly. 4.2 Quest All type systems considered here capture the fact that make ref should be applicable to monomorphic values In our system, we made no attempt at restricting the only. creation of mutable values, and concentrated on type The second test is the imperative map functional generalization instead. We were inspired by Cardelli’s given in the introduction. It illustrates the use of poly- Quest language [2], which departs significantly from morphic references as auxiliaries inside a generic func- ML, but features mutable data structures and polymor- tion. Our type system is the only one which gives a phic typing. fully polymorphic type to imperative map. The others Quest makes almost no typing restrictions for muta- restrict it to be used with monomorphic type. ble values. Soundness is ensured by different seman- The third test is the partial application of tics for type specialization. Namely, an expression with applicative map, defined in the introduction, to the polymorphic type is evaluated each time its type is spe- make ref function. It exercises the compatibility be- cialized, in contrast with ML, where it would be eval- tween functions that create mutable data and higher- uated only once. This is consistent with the fact that order functions. SML and SML-NJ refuse to generalize polymorphism is explicit in Quest programs: polymor- its type, hence reject it. They are unable to detect that phic objects are actually presented as functions that applicative map does not apply make ref immediately, take a type as argument and return a specialized ver- hence that applicative map make ref does not create sion of the object. But these semantics are incompat- any polymorphic reference. ible with ML, where generalization and specialization A simplified version of this test, shown below, is to are kept implicit in the source program.

Page 9 4.3 FX checking of such objects. Finally, some calculi of com- municating systems feature channels as first-class values The FX effect system [9] is a polymorphic type system [10]. Polymorphic typing of these channels must guar- that performs purity analysis as well: the type of an antee that senders and receivers agree on the types of expression indicates what kind of side-effects its eval- transmitted values, and this is similar to ensuring that uation can perform. This provides a simple way to writers and readers of a reference use it consistently deal with the problem of mutable values: the type of [13]. an expression cannot be generalized unless this expres- sion is referentially transparent. It is easy to see that such “pure” expressions can safely be used with differ- Acknowledgments ent types. Though this approach is attractive for other purposes Didier R´emy suggested the use of constraints in the (e.g. automatic program parallelization), it definitely typing rules. We also benefited from his expertise in does not address the main issue considered here: how type inference and unification problems. Many thanks to give the same type to semantically equivalent func- to Brad Chen for his editorial help. tions, whether written in applicative style or in impera- tive style. The reason is that the purity analysis of FX makes it apparent in the types whether a function al- A Proof of soundness locates mutable data for local purposes. For instance, imperative map and applicative map do not have the In this appendix, we sketch the proof of soundness of the same type in FX. type system with respect to the operational semantics given in section 3.3. We formalize the fact that a value v semantically be- 5 Conclusion longs to a type expression A under constraints Γ. We write this S |= v : A with Γ. The hypothesis S is a We have presented herein an extension of the ML type store typing, that is, a partial mapping from locations system that considerably enhances the support for data to type expressions. The store typing is needed to take accepting in-place modification. This is a significant into account the sharing of values introduced by the step toward the integration of polymorphic type disci- store. pline and imperative programming style. We have in- troduced the notion of closure typing, which is essential Definition 2 (Semantic typing judgements) Let to the soundness of this approach. We have given one S be a store typing, v a value, A a type, Γ a constraint type system that performs this closure typing, based sequence. The predicate S |= v : A with Γ is defined by upon labels and constraints. Our system is simple, but induction on v: falls short of being a conservative extension of ML. Fur- • S |= int(i): int with Γ. ther work includes investigating alternate, more subtle ways to typecheck closures, that would not reject any • S |= loc(`):(A ref) with Γ iff ` belongs to the well-typed ML program, while correctly keeping track domain of S, and S(`) = A. of mutable data. The scope of this work is not strictly limited to the • S |= clos(x, b, e):(A →M B) with Γ iff problem of mutable data structures. For instance, it is well-known that polymorphic exceptions raise the same – for all x ∈ Dom(e), there exists a type schema issues as references, and can be handled in the same Σ such that Σ .M is in Γ, and S |= e(x): way. More generally, similar problems arise in the in- Σ with Γ; tegration of polymorphic typing within several other – and there exists a typing environment E such programming paradigms. For instance, one approach that E[x ← A] ` b : B with Γ. to the integration of functional and logic programming is to allow partially defined values containing logical The predicate above is extended to type schemas by tak- 0 variables within a conventional functional language [15]. ing S |= v :(∀V1 ...Vn.A with Γ ) with Γ iff for Polymorphic logical variables break type safety just as all substitutions µ over V1 ...Vn, we have S |= v : polymorphic references do, and are amenable to the µA with Γ ∪ µΓ0 same treatment. In object-oriented programming, sta- Let e be an evaluation environment, and E be a typ- tus variables of objects can be seen as references sys- ing environment. We say that S |= e : E with Γ iff tematically encapsulated in functions (the methods). Dom(e) ⊆ Dom(E) and for all x ∈ Dom(e), we have Closure typing seems relevant to the polymorphic type- S |= e(x): E(x) with Γ.

Page 10 Similarly, let s be a store. We say that |= s : S Taking well-chosen substitutions µ, it follows that: with Γ if Dom(s) ⊆ Dom(S) and for all ` ∈ Dom(s), 0 we have S |= s(`): S(`) with Γ. FV (S(`) with Γ) ⊆ DV (B with Γ ∪ Γ ) \{V1 ...Vn}

In the proof of soundness, we will use the fact that the Hence the desired result: FV (S(`) with Γ) ⊆ DV (Σ semantic typing judgement is stable under substitution. with Γ) ⊆ DV (A with Γ) 2

Lemma 1 (Semantic substitution) If S |= v : A We are now ready to show the soundness of the typing with Γ, then µS |= v : µA with µΓ for all substitutions rules. µ. Proposition 2 (Soundness) Assume E ` a : A Starting from a given value v, it is not possible in with Γ. Let e be an evaluation environment, s a store, general to access any location in the store. The set R(v) S a store typing such that: of locations reachable from v is defined by induction on S |= e : E with Γ |= s : S with Γ. v, as follows. e 0 Definition 3 (Reachable locations) Assume a[s] =⇒ w. Then, w = v[s ], and there exists a store typing S0 extending S, such that: R(int(i)) = ∅ S0 |= v : A with Γ |= s0 : S0 with Γ R(loc(`)) = {`} [ R(clos(x, a, e)) = R(e(y)) Proof: By induction on the length of the evaluation. y∈Dom(e) All cases proceed as in Tofte’s proof [16, chapter 5], except when a is a let binding. Then, the last step of In the definition of S |= v : A with Γ, the types of the typing derivation is: the locations that cannot be reached from v are irrele- vant. We can actually assume any other type for those E ` a : A with Γ 0 locations: (Σ, Γ ) = Gen(A, E, Γ) E[x ← Σ] ` b : B with Γ0 Lemma 2 (Garbage collection) Let S, S0 be two 0 store typings such that S(`) = S0(`) for all ` ∈ R(v). If E ` let x = a in b : B with Γ 0 S |= v : A with Γ, then S |= v : A with Γ Applying the induction hypothesis to the first premise, Our type expressions are informative enough to allow we get va, sa, Sa extending S such that: us to connect the type of a location ` reachable from a e a[s] =⇒ v [s ] value v with the type A of v. More precisely, we have a a the following key lemma which relates the variables free Sa |= va : A with Γ |= sa : Sa with Γ. in the type of ` with the dangerous variables of A. To apply the induction hypothesis to the last premise, Lemma 3 (Store typing control) Assume S |= v : we need to show that: A with Γ. Let ` be a location in R(v). Then 0 Sa |= e[x ← va]: E[x ← Σ] with Γ FV (S(`) with Γ) ⊆ DV (A with Γ). where we write (Σ, Γ0) for Gen(A, E, Γ), and 00 Proof: By induction on v. ∀V1 ...Vn.A with Γ for Σ. Since Sa extends S, and Case 1: A = int and v = int(i). Obvious, since there V1 ...Vn are not free in E, we already have Sa |= e : are no locations reachable from v. E with Γ0 as a corollary of lemma 2. It remains to show Case 2: A = B ref, v = loc(`0), and S(`0) = B. that: By definition of the reachable locations, ` = `0. Hence 00 0 FV (S(`) with Γ) = FV (B with Γ) = DV (A with Γ). Sa |= va :(∀V1 ...Vn.A with Γ ) with Γ . L Case 3: A = A1 → A2, and v = clos(x, a, e). Let To do so, we must prove that, for any substitution µ y be a variable such that ` is reachable from e(y). Let over the V , Σ be a type scheme such that S |= e(y) : Σ and (Σ . i 0 0 00 L) ∈ Γ. We write ∀V1 ...Vn.B with Γ for Σ. For all (1) Sa |= va : µA with Γ ∪ µΓ . substitutions µ over V1 ...Vn, since S |= e(y): µB 0 with Γ ∪ µΓ , we get by induction hypothesis: Since Sa |= va : A with Γ, we have, by lemma 1,

0 0 FV (S(`) with Γ ∪ µΓ ) ⊆ DV (µB with Γ ∪ µΓ ) (2) µSa |= va : µA with µΓ.

Page 11 By definition of Gen, none of the Vi appears in any [8] M. J. Gordon, A. J. Milner, and C. P. Wadsworth. constraint of Γ0, hence Edinburgh LCF, volume 78 of Lecture Notes in Computer Science. Springer-Verlag, 1979. (3) µ(Γ) = µ(Γ0 ∪ Γ00) = Γ0 ∪ µΓ00. [9] J. M. Lucassen and D. K. Gifford. Polymorphic

By lemma 3, for any location ` reachable from va, we effect systems. In 15th symposium Principles of have FV (Sa(`) with Γ) ⊆ DV (A with Γ), and none of Programming Languages, pages 47–57. ACM Press, the Vi is dangerous in A with Γ, hence none of the Vi 1988. is free in S (`). Therefore, a [10] R. Milner, J. Parrow, and D. Walker. A calculus of mobile processes: part 1. Research report ECS- (µSa)(`) = Sa(`) for all ` ∈ R(va)(4) LFCS-89-85, University of Edinburgh, 1989. and the claim (1) above follows from (2), (3), (4), and [11] R. Milner, M. Tofte, and R. Harper. The definition lemma 2. Therefore, we can apply the induction hy- of Standard ML. The MIT Press, 1990. pothesis to the right branch of the typing derivation, getting vb, sb, Sb extending Sa such that: [12] J. C. Mitchell. Coercion and type inference. In 11th symposium Principles of Programming Lan- e[x←va] b[sa] =⇒ vb[sb] guages, pages 175–185. ACM Press, 1984.

S |= v : B with Γ0 |= s : S with Γ0 [13] J. H. Reppy. First-class synchronous operations b b b b in Standard ML. Technical Report TR 89-1068, which is the expected result. 2 Cornell University, 1989. [14] J. C. Reynolds. Toward a theory of type struc- ture. In Programming Symposium, Paris, 1974, References volume 19 of Lecture Notes in Computer Science, pages 408–425. Springer-Verlag, 1974. [1] A. W. Appel and D. B. MacQueen. Standard ML reference manual (preliminary). AT&T Bell Lab- [15] G. Smolka. FRESH: a higher-order language with oratories, 1989. Included in the Standard ML of unification and multiple results. In D. DeGroot New Jersey distribution. and G. Lindstrom, editors, Logic Programming: Functions, Relations, and Equations, pages 469– [2] L. Cardelli. Typeful programming. In E. J. 524. Prentice-Hall, 1986. Neuhold and M. Paul, editors, Formal Descrip- tion of Programming Concepts, pages 431–507. [16] M. Tofte. Operational semantics and polymorphic Springer-Verlag, 1989. type inference. PhD thesis CST-52-88, University of Edinburgh, 1988. [3] G. Cousineau and G. Huet. The CAML primer. Technical report 122, INRIA, 1990. [17] M. Tofte. Type inference for polymorphic refer- ences. Information and Computation, 89(1), 1990. [4] L. Damas. Type assignment in programming lan- guages. PhD thesis, University of Edinburgh, 1985.

[5] L. Damas and R. Milner. Principal type-schemes for functional programs. In 9th symposium Prin- ciples of Programming Languages, pages 207–212. ACM Press, 1982.

[6] Y.-C. Fuh and P. Mishra. Type inference with sub- types. In ESOP ’88, volume 300 of Lecture Notes in Computer Science, pages 94–114. Springer Ver- lag, 1988.

[7] J.-Y. Girard. Interpr´etationfonctionnelle et ´elim- ination des coupures de l’arithm´etique d’ordre sup´erieur. Th`ese d’Etat,´ Universit´e Paris VII, 1972.

Page 12