Simplifying and Improving Qualified Types

Mark P. Jones Yale University, Department of Computer Science P.O. Box 208285, New Haven, CT 06520-8285.

                 jones-mark@cs.yale.edu

Research Report YALEU/DCS/RR-1040, June 1994

Abstract

Qualified types provide a general framework for constrained type systems, with applications including type class overloading, subtyping and record calculi. This paper presents an extended version of the type inference algorithm used in previous work, that can take account of the satisfiability of constraints to obtain more accurate principal types. The new algorithm is obtained by adding two new rules, one for simplification and one for improvement of constraint sets. In particular, it permits a better treatment for the previously troublesome multiple parameter extensions of Haskell type classes. For example, a form of parametric type classes, proposed by Chen, Hudak and Odersky, can be viewed as a special case of this work.

Introduction

Qualified types provide a general framework for constrained type systems; typical applications include type class overloading, subtyping and record calculi. In previous work, we have shown how the standard treatment of ML style polymorphism can be adapted to allow the use of qualified types. In particular, we have shown that any well-typed program has a principal type that can be calculated by an extended version of Milner’s type inference algorithm. This is useful both for describing the set of types that can be assigned to a term and for detecting possible semantic ambiguities. Unfortunately, while technically correct, the principal types produced by our algorithm do not take account of the satisfiability of constraints. As a result, they are sometimes more complicated and less accurate than we might hope. Even if the additional complexity were not an issue, this can sometimes cause perfectly reasonable programs to be rejected when the principal type suggests, wrongly, that the term does not have a well-defined semantics. In other cases, the inferred types are too liberal, including unsatisfiable constraints and delaying the detection of type errors. This paper shows how these problems can be avoided by using a notion of principal satisfiable types and extending the type inference algorithm with two new rules, one dealing with simplification, the other with improvement. Our approach is also flexible enough to allow some variations between different applications of qualified types, offering better principal types without compromising the decidability of type inference. In addition to other applications, the new algorithm permits a better treatment for the previously troublesome multiple parameter extensions of Haskell type classes. In particular, we show how improvement can be used to support a form of parametric type classes [1].

Outline of this report

We start, in Section 1, with an overview of the system of qualified types used in earlier work, including examples of predicate systems, a description of the type system and an outline of the type inference algorithm. Section 2 describes the use of simplification, allowing the constraint set included in the type of an expression to be replaced by an equivalent, but simpler, set of constraints. In Section 3, we introduce the concept of improvement which is the major contribution of this report, using information about satisfiability of constraints to refine the inferred types. This requires a modification to our treatment of type inference, shifting attention to satisfiable typings and a satisfiability ordering between type schemes in Section 4. The proofs of soundness and completeness properties for a type inference algorithm that uses simplification and improvement are discussed in Section 5, with detailed proofs of new results provided in the appendix. We conclude with a discussion on the use of our new framework in Section 6.

1 A brief overview of qualified types

To describe the contributions of this report we need to begin with a brief description of the framework of qualified types on which it builds. We make no attempt to repeat the detailed presentations of [10, 13, 12] and assume some familiarity with this previous work.

1.1 Predicates

The key idea motivating the use of qualified types is the ability to include predicates in the type of a term, capturing restrictions on the ways that it can be used. The properties of predicates themselves are described using an entailment relation, denoted by the symbol ⊢⊢. If P and Q are finite sets of predicates, then the assertion that P ⊢⊢ Q means that the predicates in Q hold, whenever the predicates in P are satisfied. The only assumptions that we make about the predicate entailment relation are that it is transitive, closed under substitutions, and such that P ⊢⊢ Q whenever Q is a subset of P. Simple examples of single predicates that are useful in practical applications are illustrated in Figure 1. In some cases, we have taken the liberty of using a slightly

Predicate Interpretation

t ∈ Eq Values of type t can be tested for equality using the == operator. This usually includes all types, except those with functional components [22, 8]. t ∈ Num t is a numeric type, for example, the type of integers, or

floating point numbers, and elements of type t can be manipulated using standard arithmetic operators, for example, + for addition and ∗ for multiplication [22, 8].

t ∈ Collect(s) Values of type t can be used to represent collections of

values of type s [1].

t Dual s Values of types t and s represent the elements of dual

lattices [11].

s ⊆ t s is a subtype of t; in practice, this usually means that values of type s can be treated as values of type t by applying a suitable coercion [17, 18, 5, 4, 20].

r has l :t r is a record type containing an field labelled l of type

t [7].

r lacks l r is a record type, not including a field labelled l [7]. r_{1}#r_{2} The record types r_{1} and r_{2} do not have any fields in

common [6].

Figure 1: Examples of individual predicates and their informal intepretation

different syntax from earlier presentations, in the hope that this will make the interpretation of predicate expressions a little more obvious.

1.2 OML—Core-ML with overloading

Working towards an extension of core-ML that supports qualified types, we adopt a structured language of types specified by the grammar:

τ ::= t  type variables
| τ → τ function types
| …  other constructed types
ρ ::= P ⇒ τ qualified types
σ ::= ∀T .ρ type schemes

Here, t ranges over a given set of type variables and P and T range over finite sets of predicates and finite sets of type variables respectively. The set of type variables appearing (free) in an expression X is denoted TV (X ) and is defined in the obvious way. For programs, we use the term language of core-ML:

E ::= x  variable
| EF  application
| λx.E  abstraction
| let x = E in F local definition

For the purposes of this work, we are only interested in terms that can be assigned a type using the rules in Figure 2. These rules use judgements of the form P |A ⊢ E : σ where P is a set of predicates and A is a type assignment, i.e. a mapping from term variables to types. Much of the notation used here is standard, as indeed are most of the rules. Only (⇒I ) and (⇒E), moving global constraints in to, or out of, the type of an object, and the (∀I ) rule for polymorphic generalization, actually involve the predicate set P. We refer, collectively, to the type, term and typing rules given above as OML, a mnemonic for ‘Overloaded ML’.

1.3 Type inference for OML

An important property of OML is the existence of an algorithm for calculating principal typings for a given term. More precisely, there is an effective algorithm, taking a term E and a type assignment A as its input, for calculating the most general type that can be assigned to E, given the assumptions in A. To describe what it means for one type to be more general than another, we define an ordering between constrained type schemes, i.e. pairs of the form (P |σ) where σ is a type scheme and P is a set of predicates corresponding to global constraints

Standard rules: (var)  \begin{matrix}(x :σ) ∈ A \\ P |A ⊢ x : σ \end{matrix}

(→E)

P |A ⊢ E : τ′ → τ P |A ⊢ F : τ′
P |A ⊢ EF : τ

(→I ) (P |A_{x}, x :τ′ ⊢ E : τ) / (P |A ⊢ λx.E : τ′ → τ)

Qualified types: (⇒E)

P |A ⊢ E : Q ⇒ ρ P ⊢⊢ Q
P |A ⊢ E : ρ
(⇒I )

P, Q |A ⊢ E : ρ
P |A ⊢ E : Q ⇒ ρ
Polymorphism: (∀E)  \begin{matrix}P |A ⊢ E : ∀α.σ \\ P |A ⊢ E : [τ/α]σ \end{matrix}
(∀I )

P |A ⊢ E : σ α ∉ TV (A) ∪ TV (P)
P |A ⊢ E : ∀α.σ

Local Definition: (let)

P |A ⊢ E : σ Q |A_{x}, x :σ ⊢ F : τ

P, Q |A ⊢ (let x = E in F ) : τ

Figure 2: Typing rules for OML. on the environment in which the type scheme can be used. We start by defining the set of generic instances of a constrained type scheme, given by:

[[P′ |∀α_{i}.P ⇒ τ]] = { Q ⇒ [ν_{i}/α_{i}]τ | ν_{i} ∈ Type, Q ⊢⊢ P′, [ν_{i}/α_{i}]P }.

Using this definition, the required ordering between constrained type schemes is specified by law:

(P |σ) ≤ (P′ |σ′) ⇔ [[P |σ]] ⊆ [[P |σ′]].

In other words, σ ≤ σ′ if and only if every generic instance of σ is a generic instance of σ′. It follows immediately from the form of the definition that the ordering is reflexive and transitive. Furthermore, it is reasonably easy to show that the ordering is preserved by substitution, i.e. that S(P | σ) ≤ S(P′ | σ′), whenever (P | σ) ≤ (P′ | σ′). This is important because it indicates that the ordering on type schemes is compatible with our notion of polymorphism, allowing free type variables to be freely instantiated with arbitrary types. The type inference algorithm itself is described by the rules in Figure 3. This pre-

(var)^{W}

(x :∀α_{i}.P ⇒ τ) ∈ A β_{i} new
[β_{i}/α_{i}]P |A ⊢^{W} x : [β_{i}/α_{i}]τ

(→E)^{W}

P |TA ⊢^{W} E : τ Q |T′TA ⊢^{W} F : τ′ T′τ ∼^{U} τ′ → α α new
U (T′P, Q)|UT′TA ⊢^{W} EF : U α

(→I )^{W}

P |T (A_{x}, x :α) ⊢^{W} E : τ α new
P |TA ⊢^{W} λx.E : T α → τ

(let)^{W}

P |TA ⊢^{W} E : τ P′ |T′(TA_{x}, x :σ) ⊢^{W} F : τ′ σ = Gen(TA, P ⇒ τ)
P′ |T′TA ⊢^{W} (let x = E in F ) : τ′

Figure 3: Type inference algorithm W.

sentation, as in previous descriptions of qualified types, follows [19], using judgements of the form Q | TA ⊢^{W} E : ν where A and E are the type assignment and expression provided as inputs to the algorithm, and Q, T and ν are a predicate set, substitution and type, respectively, produced as its results. The notation Gen(A, ρ) used in the rule (let)^{W} indicates the generalization of ρ with respect to A, defined as ∀a_{i}.ρ where {a_{i}} is the set of type variables TV (ρ) \ TV (A). As demonstrated in previous work, the results of the algorithm can be used to construct a principal type scheme, η, such that:

P |A ⊢ E : σ ⟺ (P |σ) ≤ η.

Assuming, as is often the case for top-level definitions, that A does not include any free type variables, then the principal type is just:

η = Gen(A, Q ⇒ ν) = (∀a_{i}.Q ⇒ ν),

where {a_{i}} is the set of type variables appearing free in (Q ⇒ ν). Note that it is also possible for the type inference algorithm to fail, either because E contains a free variable that is not bound in A, or because the calculation of a most general unifier, described by the notation τ ∼^{U} τ′, fails as a result of a mismatch between the expected and actual type of a function argument. In this case, the completeness property of the type inference algorithm guarantees that there are, in fact, no derivable typings of the form P |A ⊢ E : σ.

2 Simplification

In this section, we will show how the principal types calculated by the type inference algorithm described above can often be simplified by using a different, but equivalent, set of predicates in the infered type. This is not a new idea; similar techniques are already used in other theoretical work, and in the implementations of systems like Haskell [8] and Gofer [16]. One of the advantages of the framework used in this report is that it allows us to view simplification independently of other aspects of the type system, revealing opportunities for specific design decisions that are sometimes hidden in other presentations of constrained type inference. Simplification of qualified types, as described in this section, has previously been suggested in [13, 12]. For convenience, we will write P ⇔ Q to indicate the equivalence of predicate sets P and Q, i.e. that P ⊢⊢ Q and Q ⊢⊢ P. It is a straightforward exercise to show that, if P ⊢⊢ Q, then:

[[∀t_{i}.P ⇒ τ]] ⊆ [[∀t_{i}.Q ⇒ τ]],

and hence that, if P ⇔ Q, then the type schemes (∀t_{i}.P ⇒ τ) and (∀t_{i}.Q ⇒ τ) are equivalent with respect to the ≤ ordering on type schemes. To see how this works in practice, suppose that the addition operator + is treated as a function of type ∀t.(t ∈ Num) ⇒ t → t → t, and consider the following examples:

• Repeated predicates: Given the term (λx.λy.λz.x + y + z), the type checking algorithm described above introduces two predicates of the form (t ∈ Num), one for each use of +, where t is the type of the parameters x, y, and z, and of the result of the λ-term. In the current setting, repeated predicates are automatically eliminated by our use of predicate sets. More generally, for example, in [14], where predicate sets are replaced by ordered sequences of predicates, simplification can be used to remove any duplicates.

• Constant predicates: Treating 1 as an integer constant of type Int, the principal type of the expression (1 + 1) produced by the algorithm above is (Int ∈ Num) ⇒ Int. Simplification corresponds to discharging the assumption that Int is included in the Num class, and allows us to treat the expression as having type Int. This is an example of the concept of constant overloading described in [13].

• Detecting type errors: Consider the function λx.(′a′ + x) which attempts to add its argument value x to a character constant ′a′. Since characters are not usually regarded as numeric values, we might expect this term to produce a type error. Instead, the standard type inference algorithm assigns a principal type of (Char ∈ Num) ⇒ Char → Char to this term. Examples like this were presented by Volpano and Smith [21] as criticisms of the original Wadler and Blott proposals for type class overloading [22], and used to motivate the use of slightly different set of typing rules that would reject such terms. In fact, the Haskell type system already has this behaviour. A similar effect can be obtained by arranging for the simplification process to fail when an unsatisfiable predicate set is encountered.

However, it is important to realize that this is a specific design decision, not an essential part of the system. For example, in a language emphasizing extensibility, we might argue that the predicate Char ∈ Num should not be treated as an error. Instead, we simply prohibit any use of a function whose context includes this predicate until some time later, when the programmer has supplied definitions to add Char to the Num class.

Another reason for avoiding a check for satisfiability in the simplification process is that it can make the type inference process undecidable. This is exactly the problem that Volpano and Smith described for an unrestricted version of the Wadler and Blott proposal. On the other hand, Haskell avoids this difficulty by imposing static constraints on the form of class and instance declarations that can be used in a program.

We will see in fact that these issues are best dealt with using the concept of improvement described in Section 3.

• Superclass constraints: The type class definitions in the Haskell standard prelude specify that Eq is a superclass of Num, i.e. that Num ⊆ Eq. This property is guaranteed by the compiler using static checks to ensure that t ∈ Eq whenever t ∈ Num. Now consider the function:

f = (λx.λy.λz.(x + y) == z).

Assuming types ∀t.(t ∈ Eq) ⇒ t → t → Bool and ∀t.(t ∈ Num) ⇒ t → t → t for the equality and addition operators, respectively, the principal type of the function f is:

∀t.(t ∈ Eq, t ∈ Num) ⇒ t → t → t → Bool.

The two predicates here correspond to the use of == and + in the definition of f . However, using the superclass relation described above, this can be simplified to just:

∀t.(t ∈ Num) ⇒ t → t → t → Bool.

• Context reduction: In Haskell, the types in a type class are described by a collection of instance declarations. For example, the standard prelude includes a declaration that includes the type [t] of lists of values of type t in the Eq class, whenever t ∈ Eq. In our formal system, this is described by an entailment (t ∈ Eq) ⊢⊢ ([t] ∈ Eq). Now suppose that we define a function:

null = (λxs.xs == [ ])

where [ ] is the empty list. The principal type for null is:

∀t.([t] ∈ Eq) ⇒ [t] → Bool.

As it stands, this type is already in the simplest form possible; the entailment given above is not sufficient to derive any other predicate set that is equivalent to ([t] ∈ Eq). However, according to the definition of Haskell, the instance declaration described above is the only way to define an equality on lists of type [t]. We could therefore argue that this predicate set is ‘observationaly equivalent’ to (t ∈ Eq), capturing this property by extending the entailment relation with ([t] ∈ Eq) ⊢⊢ (t ∈ Eq). Combined with the previous entailment, this shows that ([t] ∈ Eq) ⇔ (t ∈ Eq) and hence we can reduce the inferred type for null to:

∀t.(t ∈ Eq) ⇒ [t] → Bool.

This idea is used in Haskell to justify the use of rules for simplifying inferred types to use only predicates of the form t ∈ C where t is a type variable. This process is described as context reduction in [12].

In all these examples, we have delayed the use of simplification until the very last stage of type inference, after the calculation of the principal types. In fact, there are obvious benefits to allowing simplification to occur at earlier stages in the typing process. We can extend the algorithm in Figure 3 to allow this by adding the rule (Simp):

Q |TA ⊢^{W} E : ν P ⇔ Q
P |TA ⊢^{W} E : ν

Using (Simp) makes the type inference algorithm non-deterministic so that it is possible to obtain distinct principal types that are not equal up to renaming of bound variables. Fortunately, since these types are equivalent under the ordering ≤ introduced in Section 1.3, the addition of (Simp) still yields a sound algorithm that calculates principal type schemes for OML programs. By adopting a nondeterministic algorithm, we have the flexibility to allow designers of applications of qualified types to refine the algorithm, choosing to use simplification only under certain circumstances or at specific points during type checking. We should also comment that, although we have specified what it means for two predicates to be equivalent, we will not attempt to formalize what it means to say that one predicate set is simpler than another. There are some obvious measures of complexity that could be used, for example, the number of predicates or the size of the type expressions that they involve. However, we believe that these issues are best dealt with in the design of specific applications of qualified types. In other words, while we use a general and symmetric notion of simplification that allows any equivalent predicate set Q to be used in place of P, we would expect that, in a practical implementation, Q will actually be chosen as a simplified version of P in some appropriate manner.

3 Improvement

A second method for improving the accuracy of an inferred principal types, and the most important contribution of this report, is based on the concept of improvement. Although some special cases of this idea have been used in other systems, we are not aware of any previous work that has either identified the notion of improvement as an independent concept, or developed these ideas in the general framework described below.

3.1 Improving records

The central idea is to use information about the satisfiability of predicate sets to simplify inferred types. As a first example, consider a language with a system of records, using a function:

( .l) :: ∀r.∀t.(r has l :t) ⇒ r → t

to describe the selection of a field l of type t from a record of type r. Following conventional notation, we treat the expression e.l as a sugared version of ( .l) e. Now consider the function f = λr.(r.l, r.l) whose principal type, according to the algorithm in Section 1.3, is:

∀r.∀a.∀b.(r has l :a, r has l :b) ⇒ r → (a, b).

However, for any particular record type r, the types assigned to the variables a and b must be identical since they both correspond to the same field in r. It would therefore seem quite reasonable to treat f as having a principal satisfiable type scheme:

∀r.∀a.(r has l :a) ⇒ r → (a, a).

To capture the essence of this example in a more general setting, we introduce the following notation for describing the satisfiable instances of a given predicate set P with respect to a predicate set P_{0}:

⌊P⌋_{P_{0}} = { SP | S ∈ Subst, P_{0} ⊢⊢ SP }.

The predicate set P_{0} used here is arbitrary, although we will often use P_{0} = ∅ and we will always assume that TV (P_{0}) = ∅. In practice, the choice of P_{0} plays a relatively small part in the following and we will often omit the subscript, writing just ⌊P⌋ to avoid unnecessary distraction. It is easy to show that ⌊SP⌋ ⊆ ⌊P⌋, for any substitution S, and any predicate set P. The reverse inclusion, ⌊P⌋ ⊆ ⌊SP⌋, does not always hold, but is more interesting because it tells us that we can apply the substitution S to P without changing its satisfiable instances. In particular, taking S as the substitution [a/b], the argument about the predicates for record types given above is captured by the equality:

⌊r has l :a⌋ = ⌊r has l :a, r has l :b⌋ .

In this case, we will say that the substitution [a/b] improves the predicate set {r has l : a, r has l : b}. More generally, we write S improves P if ⌊P⌋ = ⌊SP⌋ and the only variables involved in S that do not appear in P are ‘new’ variables, similar to those introduced by the type inference algorithm in Figure 3. To make use of improvement during type inference, we extend the type inference algorithm with the rule (Imp):

Q |TA ⊢^{W} E : ν T′ improves Q
T′Q |T′TA ⊢^{W} E : T′ν

To get the most benefit from this, we would obviously prefer to use substitutions T′ that give, in some sense, the best possible improvement. However, while this is desirable, we do not make it a requirement of the work described here. This is important because the definition of general predicate systems does not guarantee the existence of ‘optimal’ improvements, or of computable algorithms for calculating them. Instead, we provide a general framework that allows us to compromise between decidability and improvement. In the simplest case, we can use the identity substitution id as an improving substitution, which satisfies id improves P for all predicate sets P. Of course, this just gives the same results as the previous version of the type inference algorithm, without improvement^{1}. In practice, the typing algorithm for a language based on the ideas presented here might be parameterized by the choice of an improving function, impr, such that (impr P) improves P for any predicate set P. The argument above shows that there is always at least one possible choice for an improving function, namely impr(P) = id. We will see that it is also possible to arrange for an improving function to fail, thereby causing the type inference algorithm to fail, if it is applied to an unsatisfiable predicate set, i.e. a predicate set P such that ⌊P⌋_{P_{0}} = ∅. This can be used to implement a type checker that produces only satisfiable typing judgements, and fails if and only if there are no satisfiable typings. Of course, this behaviour would not be appropriate for a system in which tests of the form ⌊P⌋_{P_{0}} = ∅ are not decidable. Once again, our framework allows the language designer to control these aspects of the type inference algorithm by choosing a suitable improving function.

3.2 Improving subtyping

To see why it may be necessary to introduce new variables in an improving substitution, consider a system of subtyping using predicates of the form τ ⊆ τ′ to indicate that τ is a subtype of τ′ and with an entailment relation that is fully determined by the following rules:

P ⊢⊢ τ ⊆ τ \begin{matrix}P ⊇ Q \\ P ⊢⊢ Q \end{matrix} \begin{matrix}P ⊢⊢ τ ⊆ ν & P ⊢⊢ ν ⊆ μ \\ P ⊢⊢ τ ⊆ μ \end{matrix} \begin{matrix}P ⊢⊢ τ′ ⊆ τ & P ⊢⊢ ν ⊆ ν′ \\ P ⊢⊢ (τ → ν) ⊆ (τ′ → ν′) \end{matrix}

Using an implicit coercion, the function g = λf .λx.1 + f x has principal type:

∀a.∀b.(a ⊆ (b → Int)) ⇒ a → b → Int.

Taking P_{0} = {Int ⊆ Float}, i.e. assuming that the only primitive coercion is from the type Int of integers to the type Float of floating point numbers, we can use an improving substitution [(c → d)/a] since:

⌊a ⊆ (b → Int)⌋ = ⌊(c → d) ⊆ (b → Int)⌋ .

1Pun intended!

In fact, we can obtain a further improvement by noticing that this requires d ⊆ Int, which is only possible if d = Int. Hence the ‘improved’ type for g becomes:

∀b.∀c.((c → Int) ⊆ (b → Int)) ⇒ (c → Int) → b → Int.

Now, as is often the case, improvement exposes new opportunities for simplification, and we can further refine the type of g to:

∀b.∀c.(b ⊆ c) ⇒ (c → Int) → b → Int.

3.3 Improving type classes

In this section, we will show how improvement can be used to support the use of type classes, concentrating in particular on the proposals by Chen, Hudak and Odersky [1] for parametric type classes. In the past, several researchers, including this author, have experimented with systems of multiple parameter type classes. Thinking of standard type classes as sets of types, the simplest interpretation of a multiple parameter class is as a set of tuples of types, corresponding to a relation on types. For example, a two parameter class, Dual, was used to describe duality between lattices in [11]. Unfortunately, practical experience with multiple parameter type classes in Gofer [9] suggests that the standard mechanisms for defining classes and instances in Haskell are often too weak to define useful relations between types^{2}. To illustrate the kind of problems that can occur, suppose that we use predicates of the form c ∈ Collect(a) to indicate that values of type c can be used to represent collections of values of type a. A simple class for operations on collections can be defined as follows:

class c ∈ Collect(a) where

empty :: c
insert :: a → c → c
member :: a → c → Bool

The empty value here represents an empty collection, while the insert and member functions might be used to add an element to a collection, or to test whether a particular element is included in a collection. A more realistic class definition might also include operations for removing elements from collections, for applying a function to each element of a collection, etc. There are a number of ways to implement collections. One of the simplest ways is to use a list, assuming that the values it holds can be tested for equality so that we can implement the membership test:

2Interestingly enough, these problems do not seem to occur for many useful examples involving multiple parameter constructor classes [15].

instance (a ∈ Eq) ⇒ [a] ∈ Collect(a) where

empty  = [ ]
insert x xs  = (x : xs)
member x [ ]  = False
member x (y : ys) = x == y || member x ys

A more efficient implementation can be obtained using binary search trees if we assume that there is an ordering on the elements held in a collection, captured by the type class Ord in the following definition:

data BSTree a = Empty | Fork a (BSTree a) (BSTree a)

instance (a ∈ Ord) ⇒ (BSTree a ∈ Collect(a)) where

empty  = Empty
⋮
member x Empty  = False
member x (Fork y l r) | x == y  = True
| x ≤ y  = member x l
| otherwise = member x r

On the surface, these definitions seem quite reasonable, but we soon run into difficulty if we try to use them. One of the first problems is that the type of the empty value is ∀a.∀c.(c ∈ Collect(a)) ⇒ c, which is ambiguous in the sense that a type variable a appears on the left of the ⇒ symbol, but is not mentioned on the right. As a result, there is no general way to determine the intended value of type a from the context in which empty is used. In general, it is not possible to use any term with an ambiguous principal type if we hope to provide a well-defined semantics for the language [12, 14]. Another problem is that the types assigned to member and insert are more general than we might expect. For example, it is possible to define collections that containing different types of elements and to define functions like:

int or bool :: ∀c.(c ∈ Collect(Int), c ∈ Collect(Bool)) ⇒ c → Bool
int or bool c = member 1 c || member True c

It is certainly possible that such examples might be useful in some applications. However, in many cases, we would prefer to restrict collections to hold values of a single type only, and to treat function definitions like this as type errors. The problems described above can be avoided by using parametric type classes as presented in [1, 2]. This allows us to use the same class and instance declarations as above, but to extend the compiler with additional static checks to ensure that, if P_{0} ⊢⊢ { τ ∈ Collect(ν), τ ∈ Collect(ν′) }, then ν = ν′. In effect, this means that, for any satisfiable instance of a predicate c ∈ Collect(a), the choice of type a is uniquely determined by the choice of c. Note that this can also be captured by an improving function impr such that:

ν ∼^{U} ν′

impr {τ ∈ Collect(ν), τ ∈ Collect(ν′)} = U

If no unifier exists, then the predicate set is unsatisfiable, and the improving function may fail. Other useful rules for improvement can be derived from this general rule. For example, according to the instance declarations given above, a predicate of the form [ν] ∈ Collect(ν′) can only be satisfied if ν = ν′, so we can define:

ν ∼^{U} ν′

impr {[ν] ∈ Collect(ν′)} = U

Parametric type classes are a good way of avoiding the problems with ordinary multiple parameter type classes that were sketched above. Since the choice of a in c ∈ Collect(a) is uniquely determined by the value of c, there is no need to treat empty as having an ambiguous type. In addition, the restrictions on instances of parametric type classes are exactly the conditions that we need to ensure that the definition of int or bool will be treated as a type error.

4 Taking account of satisfiability

Since the types obtained by improvement in the examples above are obviously instances of the original principal types, it is no surprise to find that our extended type inference algorithm is sound, i.e. that the typings it produces are derivable in the original typing rules given in Figure 2.

Theorem 1 If Q |TA ⊢^{W} E : ν, then Q |TA ⊢ E : ν.

On the other hand, the new algorithm is certainly not complete. Indeed, it is not even well-defined with respect to the natural equivalence on type schemes induced by the ≤ ordering. For example, the two type schemes for the function f in Section 3.1, either of which could be produced by the new algorithm, are:

σ_{1} = ∀r.∀a.(r has l :a) ⇒ r → (a, a),
σ_{2} = ∀r.∀a.∀b.(r has l :a, r has l :b) ⇒ r → (a, b).

It is easy to show that σ_{1} ≤ σ_{2}, but these types are not equivalent because σ_{2} ≰ σ_{1}. Hence the new type inference algorithm may give a result type σ_{1} that is not principal.

Even worse, there are terms that can be typed using the original rules in Figure 2, but for which the type inference algorithm may fail to produce a typing. For example, consider the expression:

λr.let (x, y) = f r in (x + 1, not y).

Using the type scheme σ_{2} for f , we can instantiate the type variables a and b to Int and Bool, respectively, to obtain a typing for this term. However, using the type scheme σ_{1}, the type inference algorithm fails because the types Int and Bool do not match. Fortunately, the key to solving this problem is to notice that, although we can obtain a typing for this term using type scheme σ_{2}, the corresponding predicate set, {r has l : Int, r has l : Bool}, is not satisfiable. We will see that the problems described above can be avoided by:

• Proving completeness of the algorithm with respect to a weaker ordering that identifies type schemes with the same set of satisfiable instances: Just as the original ordering on type schemes was defined in terms of the set of generic instances of a type scheme, we will define the new ordering in terms of the generic satisfiable instances of a type scheme with respect to a predicate set P_{0}. The set of generic satisfiable instances of a type scheme is defined by:

[[P′ |∀α_{i}.P ⇒ τ]]_{P_{0}}^{sat} = { [ν_{i}/α_{i}]τ | ν_{i} ∈ Type, P_{0} ⊢⊢ P′, [ν_{i}/α_{i}]P }.

The satisfiability ordering, again with respect to P_{0}, can now be defined using:

(P |σ) ≤_{P_{0}}^{sat} (P′ |σ′) ⇔ [[P |σ]]_{P_{0}}^{sat} ⊆ [[P′ |σ′]]_{P_{0}}^{sat}.

Clearly, ≤^{sat} is both reflexive and transitive. It is also quite easy to show that it is weaker than ≤, i.e. that:

(P |σ) ≤ (P′ |σ′) ⇒ (P |σ) ≤_{P_{0}}^{sat} (P′ |σ′).

On the other hand, unlike ≤, the satisfiability ordering is not preserved by substitution^{3}.

• Restricting our attention to satisfiable typings: A typing of the form P |A ⊢ E : σ is of no practical use if the predicates in P do not hold or if there is no way to satisfy the predicates involved in σ. We can capture these conditions formally by defining:

3As a counter example, consider a predicate a ∈ C for which the only satisfiable instance is Int ∈ C . Now consider the qualified types η_{i} = (a_{i} ∈ C ) ⇒ a_{i}, for i = 1, 2, and let S = [Int/a_{1}]. Then [[η_{1}]]^{sat} = ∅ = [[η_{2}]]^{sat}, and hence η_{1} ≤^{sat} η_{2}, but [[Sη_{1}]]^{sat} = {Int} while [[Sη_{2}]]^{sat} = [[η_{2}]]^{sat} = ∅, so Sη_{1} ≰ Sη_{2}.

P_{0} sat (P′ |σ) ⇔ [[P′ |σ]]_{P_{0}}^{sat} ≠ ∅.

The following properties of this relationship between predicate sets and type schemes are easily established and show that this notion of satisfiability is well-behaved with respect to polymorphism (i.e. instantiating free variables), entailment and ordering:

– If P_{0} sat (P |σ), then SP_{0} sat S(P |σ) for any substitution S.

– If P_{0} sat (P |σ) and Q_{0} ⊢⊢ P_{0}, then Q_{0} sat (P |σ).

– If P_{0} sat (P′ |σ′) and (P′ |σ′) ≤_{P_{0}}^{sat} (P |σ), then P_{0} sat (P |σ).

There is one further problem that occurs when an unused let-bound variable is assigned an unsatisfiable type scheme. As an illustration, consider the following type assignment and predicate set:

A = { f :∀α.α ∈ C ⇒ α → α, z :∀α.α ∈ D ⇒ α }
P = { a ∈ C , a ∈ D }

where C and D are disjoint singletons, say C = { Int } and D = { Bool }, and hence:

[Int/α] improves { α ∈ C } and [Bool/α] improves { α ∈ D }.

Using the original typing rules for OML, we can construct a derivation of the form:

(P′, P)|A ⊢ f : a → a (P′, P)|A ⊢ z : a
(P′, P)|A ⊢ f z : a
P′ |A ⊢ f z : σ

P′ |A, x :σ ⊢ F : τ

P′ |A ⊢ let x = f z in F : τ

where σ = (∀a.(a ∈ C , a ∈ D) ⇒ a). Clearly σ is not satisfiable, but, if the bound variable x does not appear free in F , then there is no reason for these unsatisfiable constraints to be reflected by the predicates in P′. However, in the type inference algorithm, using improvement immediately after the introduction of the variables f and z would produce typings of the form:

Q |A ⊢ f : Int → Int and Q |A ⊢ z : Bool

and the algorithm will fail to infer a type for the expression f z. The problem here is that, since x ∉ FV (F ), the use of generalization in the typing rule for let-expressions allows us to hide, and then discard unsatisfiable constraints.

More generally, we will describe an expression of the form let x = E in F where x ∉ FV (F ) as a redundant let-binding. Such bindings serve no practical purpose, except:

• To add extra typing constraints to a term, for example, the principal type of λx.x is ∀t.t → t, but the principal type of λx.let z = x +1 in x is Int → Int.

• To control the sequencing of effects, for example in a call-by-value language like Standard ML.

In each case, there are more elegant ways to achieve the same effect, without using a redundant binding. However, a more general approach is simply to replace any redundant bindings with corresponding expressions of the form (λx.F )E. Since x ∉ FV (F ), this term is well-typed if, and only if, the original expression is welltyped. With these observations, it is reasonable to restrict our attention to terms with no redundant let-bindings. Obviously, if this property holds for a given term E, then it also holds for all subterms of E, a fact that will be used implicitly in proofs by induction. Given the definitions above, we can now state the main completeness result for a type inference algorithm that supports both simplification and improvement rules:

Theorem 2 Suppose that P |A ⊢ E : σ and P_{0} sat (P |σ) where E is a term with no redundant let-bindings and TV (A) = ∅. Then the type inference algorithm for E in A will not fail, and, for any Q and ν such that Q |A ⊢^{W} E : ν, we have:

(P |σ) ≤_{P_{0}}^{sat} Gen(A, Q ⇒ ν).

This result indicates that, if a term E has any satisfiable typings for a set A of typing assumptions, then there is a principal type η = Gen(A, Q ⇒ ν) which is more general than every satisfiable typing for E in A. In fact, the principal type is itself a satisfiable typing for E in A:

• Satisfiability follows directly from the fact that it is an upper bound of a non-empty set of satisfiable constrained type schemes.

• To see that η is a typing for E in A, we can use the soundness result above (Theorem 1) to show that Q | A ⊢ E : ν, and then use (⇒I ) and (∀I ) to obtain a derivation ∅|A ⊢ E : η.

It is also possible to state a more general version of the completeness theorem without the requirement that TV (A) = ∅. However, in practice, this special case is usually of most interest, corresponding to the process of calculating the type for a top-level definition in a Haskell or ML program.

5 Proof of soundness and completeness

The main purpose of this section is to prove the soundness and completeness results stated in the previous section as Theorem 1 and Theorem 2, respectively. This is a complicated task that requires careful management and structuring. Fortunately, we can reduce the amount of work involved by building on the results of previous work. The diagram in Figure 4 summarizes the main results for the original

⊢

Completeness: Suppose that P |SA ⊢ E : σ. Then Q |TA ⊢^{W} E : ν and there is a substitution R such that S ≈ RT and (P |σ) ≤ RGen(TA, Q ⇒ ν).

Completeness^{s}: If P | A ⊢ E : σ, then there is a set of predicates P′ and a type τ such that P′ |A ⊢^{s} E : τ and (P |σ) ≤ Gen(A, P′ ⇒ τ).

Completeness^{W}: If P | SA ⊢^{s} E : τ, then Q |TA ⊢^{W} E : ν and there is a substitution R such that S ≈ RT , τ = Rν and P ⊢⊢ RQ.

⊢^{s}

Soundness^{s}: If P | A ⊢^{s} E : τ, then P |A ⊢ E : τ.

Soundness^{W}: If P | TA ⊢^{W} E : τ,
then P |TA ⊢^{s} E : τ.

Soundness: If P |TA ⊢^{W} E : τ, then P |A ⊢ E : τ.

⊢^{W}

Figure 4: A summary of the original soundness and completeness results.

system of qualified types presented in [13, 12] and describing the relationship between typing judgements in three different systems:

• The original typing rules for OML (Figure 2), described by judgements using the ⊢ symbol.

• The type inference algorithm (Figure 3), described by judgements using the ⊢^{W} symbol.

• A collection of ‘syntax-directed’ typing rules described by judgements using the ⊢^{s} symbol. This system provides a convenient stepping stone between the original typing rules and the type inference algorithm, and will be described in more detail below.

Following convention, the results in the top portion of Figure 4 are described as completeness properties, while those in the lower portion are referred to as soundness properties. In each case, the main result linking the ⊢ and ⊢^{W} systems can be obtained from the corresponding properties involving ⊢^{s} . The original typing rules for OML are not suitable for type inference: there are many different ways that the rules can be applied to a given term, but it is not clear which, if any, will lead to a principal type. In earlier work, following [3], we avoided these problems by defining a syntax-directed system and proving its equivalence with the original type system using the theorems labelled Soundness^{s} and Completeness^{W} in Figure 4. The most important property of the syntaxdirected system is that the the structure of every typing derivation is uniquely determined by the syntactic structure of the term involved. The typing rules for the syntax-directed system are given in Figure 5 using judgements of the form P |A ⊢^{s} E : τ.

(var)^{s}  \begin{matrix}(x :σ) ∈ A & (P ⇒ τ) ≤ σ \\ P |A ⊢^{s} x : τ \end{matrix}

(→E)^{s}

P |A ⊢^{s} E : τ′ → τ P |A ⊢^{s} F : τ′
P |A ⊢^{s} EF : τ

(→I )^{s}

P |A_{x}, x :τ′ ⊢^{s} E : τ
P |A ⊢^{s} λx.E : τ′ → τ

(let)^{s}

P |A ⊢^{s} E : τ P′ |A_{x}, x :σ ⊢^{s} F : τ′ σ = Gen(A, P ⇒ τ)
P′ |A ⊢^{s} (let x = E in F ) : τ′

Figure 5: Syntax-directed inference system.

Several useful properties of the syntax-directed system were established in [13, 12], including:

• If P |A ⊢^{s} E : τ and S is a substitution, then SP |SA ⊢^{s} E : Sτ.

• If P |A ⊢^{s} E : τ and Q ⊢⊢ P, then Q |A ⊢^{s} E : τ.

• If P |A′ ⊢^{s} E : τ and A′ ≤ A, then P |A ⊢^{s} E : τ.

For the purposes of the work described in this report, the first two properties are important because they are exactly what we need to establish the soundness properties for the rules (Imp) and (Simp), respectively, in the proof of Theorem 1 (The remaining cases are the same as in the original proof of Soundness^{W}).

The expression A ≤ A′ in the third property indicates that the two type assignments A and A′ have the same domain and that A(x) ≤ A′(x) for each variable x bound in A. In the following, we will use the obvious counterpart to describe when one type assignment A′ is more general than another A with respect to the satisfiability ordering, written A ≤_{P_{0}}^{sat} A′. The third property plays an important role in the proof of Completeness^{W} in [12]. Unfortunately, the corresponding result with ≤ replaced by ≤_{P_{0}}^{sat} does not hold. A simple counterexample can be obtained from the definitions on Page 17, since (P′, P) | A ⊢^{s} f z : a, and A ≤_{P_{0}}^{sat} A′ = { f :(Int → Int), z :Bool }, but there is no derivable typing for the expression f z using the assignments in A′. We avoid this problem in the completeness theorem below by including an assumption of the form A ≤_{P_{0}}^{sat} SA′ as an extra hypothesis. A similar technique is used in Smith’s thesis [20]. In the remaining part of this section, we will describe a replacement for the Completeness^{W} result in Figure 4 that allows the use of the rules (Simp) and (Imp) at arbitrary points during type inference. Combined with Completeness^{s}, we will show how this can be used to establish Theorem 2, an analogue of the Completeness result at the top of Figure 4. Our first task is to justify the informal comments about terms with no redundant let-bindings in Section 4. Using the syntax-directed typing rules, the following theorem shows that, the class constraints for a variable x appearing free in an expression E will be reflected by the constraints P on the use of E itself:

Proposition 3 Suppose that P | A ⊢^{s} E : τ, x ∈ FV (E), A(x) = (∀α_{i}.Q ⇒ ν), and that E has no redundant let-bindings. Then there are types τ_{i} such that P ⊢⊢ [τ_{i}/α_{i}]Q.

In particular, if we have a satisfiable syntax-directed typing for a term E with respect to some set of assumptions A, and if the variable x appears free in E, then the type assigned to x in A must also be satisfiable:

Corollary 4 Suppose that P | A_{x}, x : Gen(A, Q ⇒ ν) ⊢^{s} E : τ, x ∈ FV (E), P_{0} sat Gen(A, P ⇒ τ), and that E has no redundant let-bindings. Then:

P_{0} sat Gen(A, Q ⇒ ν).

Working towards a completeness result for the type inference algorithm with respect to the syntax-directed type system, suppose that we have a derivation P |A ⊢^{s} E : τ. Our goal is to prove that:

• The type inference algorithm will not fail to find a type for E in A. Since the algorithm may fail if E does not have any satisfiable typings, it will be necessary to restrict our completeness result to satisfiable syntax-directed derivations, i.e. to derivations P |A ⊢^{s} E : τ such that P_{0} sat Gen(A, P ⇒ τ).

• If the type inference algorithm produces a typing Q | TA ⊢^{W} E : ν, then the corresponding type scheme Gen(TA, Q ⇒ ν) is more general than the type assigned to E in the syntax-directed system, i.e. we want to show that:

Gen(A, P ⇒ τ) ≤_{P_{0}}^{sat} Gen(TA, Q ⇒ ν).

In fact, to carry out the required proof, it is necessary to generalize the hypotheses a little, allowing the use of distinct type assignments, A in the original syntax-directed typing, and A′ in the type inference algorithm, related by A ≤_{P_{0}}^{sat} SA′ for some substitution S.

Motivated in part by these comments, we use the following theorem to express the completeness of the type inference algorithm with respect to the syntax-directed rules, and the satisfiability ordering, ≤_{P_{0}}^{sat}:

Theorem 5 Suppose that P | A ⊢^{s} E : τ, P_{0} sat Gen(A, P ⇒ τ), A ≤_{P_{0}}^{sat} SA′, and that E does not contain any redundant let-bindings. Then the type inference algorithm will not fail, and for every Q | TA′ ⊢^{W} E : ν, there is a substitution R such that:

RT ≈ S and Gen(A, P ⇒ τ) ≤_{P_{0}}^{sat} RGen(TA′, Q ⇒ ν).

The proof of this theorem is a little complex; full details are included in the appendix. However, with this result in hand, the proof of Theorem 2 is straightforward. Suppose that P | A ⊢ E : σ and P_{0} sat (P | σ) where E is a term with no redundant let-bindings and TV (A) = ∅. By Completeness^{s}, we know that P′ |A ⊢^{s} E : τ′ for some P′ and τ′ such that:

(P |σ) ≤ Gen(A, P′ ⇒ τ′).

From the properties of ≤_{P_{0}}^{sat}, it follows that (P | σ) ≤_{P_{0}}^{sat} Gen(A, P′ ⇒ τ′), and hence, since P_{0} sat (P | σ), that P_{0} sat Gen(A, P′ ⇒ τ′). Since A ≤_{P_{0}}^{sat} A, we can use Theorem 5 to show that the type inference algorithm will not fail and that, for each Q |TA′ ⊢^{W} E : ν, there is a substitution R such that Gen(A, P′ ⇒ τ′) ≤_{P_{0}}^{sat} RGen(TA, Q ⇒ ν). Since TV (A) = ∅, we know that Gen(TA, Q ⇒ ν) has no free variables and that TA = A. Combining the two ≤_{P_{0}}^{sat} orderings above, we obtain:

(P |σ) ≤_{P_{0}}^{sat} Gen(A, Q ⇒ ν)

as required.

6 Discussion

The ideas described in this paper provide a general and modular framework for the design of constrained type systems, taking advantage of information about satisfiability of constraints to infer more accurate and informative principal types. The design of specific applications of our framework starts with the choice of a system of predicates and an entailment relation, as described in Section 1.1. Without any further work, the original type inference algorithm presented in Figure 3, can be used to calculate principal typings for the corresponding system of qualified types. Extending the algorithm with rules for simplification and improvement leads to a non-deterministic type inference algorithm. This allows us to choose how the rules will be combined in particular ways to provide a deterministic algorithm for use in practical implementations. For a more algorithmic flavour, we would normally expect the implementation of simplification and improvement to be described by (deterministic) functions, rather than the more general ‘⇔’ and ‘improves’ relations used by the presentations in Sections 2 and 3, respectively:

• A simplifying function, simp, mapping predicate sets P to appropriate ‘simplified’ versions, simp P, might can be used to implement simplification. The only condition that a simplifying function must satisfy is that P ⇔ (simp P), for all predicate sets P. In this setting, the inference rule (Simp) introduced in Section 2 might be replaced by:

Q |TA ⊢^{W} E : ν P = simp Q
P |TA ⊢^{W} E : ν

For any predicate system, the identity function specified by simp P = P can be used as a simplifying function. However, more interesting, and more useful functions can be used in specific applications.

• In a similar way, an improving function, impr, mapping sets of predicates to suitable improving substitutions, can be used to implement improvement, as described in Section 3.1. The correctness of an improving function can be specified by the requirement that (impr P) improves P, for all predicate sets P, and the (Imp) inference rule introduced in Section 3 can be rewritten to use an improving function:

Q |TA ⊢^{W} E : ν T′ = impr Q
T′Q |T′TA ⊢^{W} E : T′ν

The trivial improving function, impr P = id can be used with any system of predicates, but it is often possible to find more useful definitions.

Note that it is possible to arrange for simplifying or improving functions to fail if they are applied to an unsatisfiable predicate set, causing the type inference algorithm to fail as a result. However, while this may be useful in some applications, it is not required; in the general case, testing for satisfiability of a predicate set is undecidable and we would not be able to guarantee termination of the type inference algorithm (see [21], for example). For similar reasons, while we expect the results of simplifying and improving functions to satisfy certain correctness conditions, we do not insist that they are ‘optimal’; in the general case, there may not be any effective way to find such optimal solutions. Our approach is to leave the task of finding suitable simplifying and improving functions to the designer of specific applications of qualified types. In this way, designers retain control over the balance between making good use of simplification and improvement, and ensuring that type inference remains tractable. This is in contrast to earlier work, for example, in [20], where full tests for satisfiability of predicate sets are needed and strong restrictions on the definition of predicate entailments are needed to guarantee a decidable type inference process. One of the most obvious places to use simplification and improvement is immediately before generalizing the type of a let-bound variable. For example, we might calculate the type of a let-expression with a derivation of the form:

P |TA ⊢^{W} E : ν T′ = impr P
T′P |T′TA ⊢^{W} E : T′ν

Q = simp (T′P)
Q |T′TA ⊢^{W} E : T′ν

P′ |T′′(T′TA_{x}, x :σ) ⊢^{W} F : τ′

P′ |T′′T′TA ⊢^{W} (let x = E in F ) : τ′

where σ = Gen(T′TA, Q ⇒ T′ν). This can be packaged up as a new inference rule for typing let-expressions to replace the original (let)^{W} rule:

P |TA ⊢^{W} E : ν T′ = impr P Q = simp (T′P) P′ |T′′(T′TA_{x}, x :σ) ⊢^{W} F : τ′
P′ |T′′T′TA ⊢^{W} (let x = E in F ) : τ′

Of course, we probably could have started out with this rule at the very beginning. However, our approach seems much more attractive and modular since it allows us to view the typing of let-expressions and the treatment of simplification and improvement as independent concerns and to combine them in ways that are not captured by the rule above. In there current state, the ideas presented in the report are of most use to language designers, not to programmers. For example, the design of a type inference algorithm for a language with parametric type classes can be based on the framework and algorithms presented here. It would also be interesting to explore more ambitious language designs that provide the programmer with the ability to define and extend simplifying and improving functions. Such a system might be used to support parametric type classes as a programmable extension of the language, rather than a built-in part of the type system. One way that this might be achieved for the Collect class described in Section 3.3 is to augment the class and instance declarations given there with a declaration of the form:

improve (a ∈ Collect(b), a ∈ Collect(c)) using b = c

The biggest problems with this approach would be to ensure that the compiler is able to enforce the restrictions on instance definitions that this declaration implies, and to maintain decidability of type inference. In passing, we mention that this improve P using S construct can be used to provide similar behaviour to the ‘default’ mechanism in Haskell [8, Section 4.3.4], for example:

improve (a ∈ Num, a ∈ Integral) using a = Int

However, there are some significant theoretical problems with the current default mechanism causing a loss of coherence in some cases. The same is true for the improving rule given above which is not actually valid in Haskell since the standard prelude provides at least two distinct types, Int and Integer, both of which can be used as solutions for the predicates (a ∈ Num, a ∈ Integral). We should also note that the use of information about satisfiability must be balanced against the goals of extensibility. For example, consider a module in which the only class and instance declarations are:

class a ∈ C where

compare :: a → a → Bool

instance Int ∈ C where …

In this situation, we might be tempted to add an implicit improving rule of the form:

improve (a ∈ C ) using a = Int.

However, this would not be valid if, in another module in the same program, we tried to extend the class C with an instance:

instance Float ∈ C where …

With this example in mind, it is probably better to require improving declarations to be written explicitly by the programmer, and to be exported from a module as part of the definition of a class.

The work described here provides simple correctness criteria for simplifying and improving functions, but it does not provide any further insights into the construction of such functions for specific applications. For example, the task of simplifying predicate sets containing subtyping constraints has been studied in some depth by several researchers, including [17, 18, 5, 4, 20]. This report does not subsume the results of those papers. Rather, it aims to provide a general framework, to which they may be applied in the design of type systems combining polymorphism and subtyping.

Acknowledgements

This work was supported in part by a grant from ARPA, contract number N00014-91-J-4043. It is a pleasure to thank Kung Chen for useful discussions about the ideas presented in this report, and for helpful insights into his own work on parametric type classes [2]. The concept of principal satisfiable type schemes was originally introduced in [12, Chapter 6]. At that time, we conjectured that every term with a satisfiable typing could be assigned a principal satisfiable typing, a fact which we have, finally, had the opportunity to prove!

Appendix: Proofs

This appendix contains detailed proofs for the main new results presented in this report. For convenience, we repeat the statement of each result in a box at the beginning of the corresponding proof.

Proposition 3 Suppose that P |A ⊢^{s} E : τ, x ∈ FV (E), A(x) = (∀α_{i}.Q ⇒ ν), and that E has no redundant let-bindings. Then there are types τ_{i} such that P ⊢⊢ [τ_{i}/α_{i}]Q.

By induction on the structure of P |A ⊢^{s} E : τ:

Case (var)^{s}: We have a derivation of the form:

\begin{matrix}(x :σ) ∈ A & (P ⇒ τ) ≤ σ \\ P |A ⊢^{s} x : τ \end{matrix}

where σ = A(x) = (∀α_{i}.Q ⇒ ν). Since (P ⇒ τ) ≤ σ, it follows that P ⊢⊢ [τ_{i}/α_{i}]Q, giving an entailment of the required form.

Case (→E)^{s}: We have a derivation of the form:

P |A ⊢^{s} E : τ′ → τ P |A ⊢^{s} F : τ′
P |A ⊢^{s} EF : τ

Since x ∈ FV (EF ) = FV (E) ∪ FV (F ), either x ∈ FV (E) or x ∈ FV (F ). The result follows by induction on either the first or second hypothesis, respectively.

Case (→I )^{s}: We have a derivation of the form:

P |A_{y}, y :τ′ ⊢^{s} E : τ
P |A ⊢^{s} λy.E : τ′ → τ

Since x ∈ FV (λy.E), we know that x ≢ y and that x ∈ FV (E). Thus (x :σ) ∈ (A_{y}, y :τ′) and the result follows by induction.

Case (let)^{s}: We have a derivation of the form:

P |A ⊢^{s} E : τ P′ |A_{y}, y :σ ⊢^{s} F : τ′
P′ |A ⊢^{s} (let y = E in F ) : τ′

where σ = Gen(A, P ⇒ τ). By hypothesis, x ∈ FV (let y = E in F ) = FV (E) ∪ (FV (F ) \ {y}) so there are two cases to consider:

• If x ∈ FV (E), then P ⊢⊢ [τ_{i}/α_{i}]Q by induction on the first hypothesis. Note that y ∈ FV (F ) since we consider only terms with no redundant let-bindings. Writing σ = (∀β_{j} .P ⇒ τ), it follows by induction on the second hypothesis that P′ ⊢⊢ [ν_{j} /β_{j} ]P for some types ν_{j} . Combining these entailments, we obtain P′ ⊢⊢ [ν_{j} /β_{j} ]([τ_{i}/α_{i}]Q). Note that the only variables appearing free in Q, other than those in {α_{i}}, also appear free in A. However, by definition of σ, none of β_{j} appears free in A, and hence the entailment above is equivalent to:

P′ ⊢⊢ [[ν_{j} /β_{j} ]τ_{i}/α_{i}]Q

which has the required form.

• If x ∈ FV (F ) and x ≢ y, then the result follows directly by induction on the second hypothesis.

This completes the proof. □

Corollary 4 Suppose that P | A_{x}, x : Gen(A, Q ⇒ ν) ⊢^{s} E : τ, x ∈ FV (E), P_{0} sat Gen(A, P ⇒ τ), and that E has no redundant let-bindings. Then:

P_{0} sat Gen(A, Q ⇒ ν).

For convenience, we will write:

σ = Gen(A, P ⇒ τ) = ∀α_{i}.P ⇒ τ
σ′ = Gen(A, Q ⇒ ν) = ∀β_{j} .Q ⇒ ν

By hypothesis, P_{0} sat σ, and hence there are types τ_{i} such that P_{0} ⊢⊢ [τ_{i}/α_{i}]P. By Proposition 3, P ⊢⊢ [ν_{j} /β_{j} ]Q for some types, ν_{j} . Combining these entailments gives:

P_{0} ⊢⊢ [τ_{i}/α_{i}]([ν_{j} /β_{j} ]Q).

However, The only variables that appear free in Q, other than β_{j} , also appear free in A, but none of α_{i} appears free in A, so the entailment above is equivalent to:

P_{0} ⊢⊢ [[τ_{i}/α_{i}]ν_{j} /β_{j} ]Q.

It follows that P_{0} sat σ′, as required. □

Theorem 5 Suppose that P |A ⊢^{s} E : τ, P_{0} sat Gen(A, P ⇒ τ), A ≤_{P_{0}}^{sat} SA′, and that E does not contain any redundant let-bindings. Then the type inference algorithm will not fail, and for every Q | TA′ ⊢^{W} E : ν, there is a substitution R such that:

RT ≈ S and Gen(A, P ⇒ τ) ≤_{P_{0}}^{sat} RGen(TA′, Q ⇒ ν).

The proof of this theorem is quite complicated and will therefore be presented in three parts:

• In the first part, we show that the theorem can be proved using an auxiliary result.

• In the second part, we prove the auxiliary result by structural induction.

• In the third part, we consider the use of the rules (Simp) and (Imp) for simplification and improvement, respectively.

First part: For the proof the main theorem, we will concentrate on establishing the following auxiliary result:

If P |A ⊢^{s} E : τ, A ≤_{P_{0}}^{sat} SA′, and P_{0} ⊢⊢ S′P for some S′ with S′A = A, then the type inference algorithm will not fail, and for every Q |TA′ ⊢ W E : ν, there is a substitution R such that:

RT ≈ S, P_{0} ⊢⊢ RQ, and S′τ = Rν.

To see why this is sufficient, note that:

• P_{0} sat Gen(A, P ⇒ τ) guarantees the existence of at least one substitution S′ such that P_{0} ⊢⊢ S′P and S′A = A. This, in turn, guarantees that Q | TA ⊢^{W} E : ν for some Q, T and ν, independent of S′.

• Given the existence of a derivation for the type inference algorithm, the auxiliary result above provides exactly the hypotheses needed to apply Lemma 6 below, and hence to prove the existence of a substitution R such that:

RT ≈ S and Gen(A, P ⇒ τ) ≤_{P_{0}}^{sat} RGen(TA′, Q ⇒ ν).

Second part: We prove the auxiliary result by induction on the structure of P |A ⊢^{s} E : τ.

Case (var)^{s}: We have a derivation of the form:

\begin{matrix}(x :σ) ∈ A & (P ⇒ τ) ≤ σ \\ P |A ⊢^{s} x : τ \end{matrix}

where σ = A(x) = (∀α_{i}.Q ⇒ ν). Let σ′ = A′(x) = (∀β_{j} .Q′ ⇒ ν′) and note that, by hypothesis, σ ≤_{P_{0}}^{sat} Sσ′.

Since S′A = A and P_{0} ⊢⊢ S′P, it follows that:

S′τ ∈ [[Gen(A, P ⇒ τ)]]_{P_{0}}^{sat} ⊆ [[σ]]_{P_{0}}^{sat} ⊆ [[Sσ′]]_{P_{0}}^{sat}.

As an aside, the first inclusion follows from the fact that, if ρ = (P ⇒ τ) ≤ σ, then any variable appearing free in ρ, but not in A, does not appear free in σ, and hence Gen(A, ρ) ≤ σ. The inclusion follows from the fact that ≤_{P_{0}}^{sat} is weaker than ≤ and from the definition of ≤_{P_{0}}^{sat}.

Choosing new variables γ_{j} , we have Sσ′ = ∀γ_{j} .S[γ_{j} /β_{j} ](Q′ ⇒ ν′). Hence, by (var)^{W}, there is a derivation:

[γ_{j} /β_{j} ]Q′ |A′ ⊢^{W} x : [γ_{j} /β_{j} ]ν′.

Since S′τ ∈ [[Sσ′]]_{P_{0}}^{sat}, there are types ν_{j} such that:

S′τ = [ν_{j} /γ_{j} ](S[γ_{j} /β_{j} ]ν′) = S[ν_{j} /β_{j} ]ν′ = S[ν_{j} /γ_{j} ]([γ_{j} /β_{j} ]ν′)

and:

P_{0} ⊢⊢ [ν_{j} /γ_{j} ](S[γ_{j} /β_{j} ]Q′) = S[ν_{j} /β_{j} ]Q′ = S[ν_{j} /γ_{j} ]([γ_{j} /β_{j} ]Q′).

Let R = S[ν_{j} /γ_{j} ]. Then R ≈ S, S′τ = R([γ_{j} /β_{j} ]ν′), and P_{0} ⊢⊢ R([γ_{j} /β_{j} ]Q′).

Case (→E)^{s}: We have a derivation of the form:

P |A ⊢^{s} E : τ′ → τ P |A ⊢^{s} F : τ′
P |A ⊢^{s} EF : τ

By induction, Q | TA′ ⊢^{W} E : ν and there is a substitution R such that RT ≈ S, P_{0} ⊢⊢ RQ, and S′(τ′ → τ) = Rν.

In a similar way, since A ≤_{P_{0}}^{sat} SA′ = R(TA′), it follows by induction on the second hypothesis that Q′ | T′TA ⊢^{W} F : ν′ and there is a substitution R′ such that R′T′ ≈ R, P_{0} ⊢⊢ R′Q′, and S′τ′ = R′ν′.

Pick a new variable α and let R′′ = R′[S′τ/α]. Note that:

R′′(T′ν) = R′T′ν
= Rν
= S′(τ′ → τ)
= S′τ′ → S′τ
= R′ν′ → S′τ
= R′′(ν′ → α)

and hence R′′ is a unifier of T′ν and ν′ → α. It follows that T′ν ∼^{U} (ν′ → α) for some most general unifier U such that R′′ = U′U for some U′.

By (→E)^{W}, there is a derivation:

U (T′Q, Q′)|UT′TA′ ⊢^{W} EF : U α.

Note that S ≈ RT ≈ R′T′T ≈ R′′T′T ≈ U′UT′T , U′(U α) = R′′α = S′τ and:

P_{0} ⊢⊢ (RQ, R′Q′) = (R′T′Q, R′Q′) = R′′(T′Q, Q′) = U′(U (T′Q′, Q′)).

Case (→I )^{s}: We have a derivation of the form:

P |A_{x}, x :τ′ ⊢^{s} E : τ P |A ⊢^{s} λx.E : τ′ → τ Let α be a new variable and set S′′ = S[S′τ′/α] so that:

(A_{x}, x :τ′) ≤_{P_{0}}^{sat} S′′(A′_{x}, x :α).

By induction, Q | T (A′_{x}, x : α) ⊢^{W} E : ν for some Q, T and ν and there is a substitution R such that S′′ ≈ RT , P_{0} ⊢⊢ RQ and S′τ = Rν.

By (→I )^{W}, we have Q | TA′ ⊢^{W} λx.E : T α → ν. We already know that P_{0} ⊢⊢ RQ, and S ≈ S′′ ≈ RT . To complete the proof for this case, note that:

R(T α → ν) = (RT α → Rν) = (S′′α → S′τ) = S′(τ′ → τ).

Case (let)^{s}: We have a derivation of the form:

P |A ⊢^{s} E : τ P′ |A_{x}, x :σ ⊢^{s} F : τ′ σ = Gen(A, P ⇒ τ)
P′ |A ⊢^{s} (let x = E in F ) : τ′

Since we ignore terms containing redundant let-bindings, we can assume that x ∈ FV (F ). By hypothesis, P_{0} sat Gen(A, P′ ⇒ τ′) using the substitution S′, and hence P_{0} sat σ by Corollary 4.

By induction, using the original statement of the theorem in the form given in the box at the start of the proof, there is a derivation Q | TA′ ⊢^{W} E : ν and a substitution R such that:

RT ≈ S and σ ≤_{P_{0}}^{sat} RGen(TA′, Q ⇒ ν).

This use of induction can be justified by repeating the the argument in the first part above.

Writing σ′ = RGen(TA′, Q ⇒ ν), we know that

σ ≤_{P_{0}}^{sat} Rσ′ and A_{x} ≤_{P_{0}}^{sat} SA′_{x} = R(TA′_{x}),

and hence (A_{x}, x : σ) ≤_{P_{0}}^{sat} R(TA′_{x}, x : σ′). By induction, there is a derivation Q′ |T′(TA′_{x}, x :σ′) ⊢^{W} F : ν′ and there is substitution R′ such that R′T′ ≈ R, P_{0} ⊢⊢ R′Q′, and S′τ′ = R′ν′.

By (let)^{W} there is a derivation:

Q′ |T′TA′ ⊢^{W} (let x = E in F ) : ν′

and R′T′T ≈ RT ≈ S, P_{0} ⊢⊢ R′Q′, and S′τ′ = R′ν′.

Third part: The only type inference rules used in the proof of the second part above are (var)^{W}, (→E)^{W}, (→I )^{W} and (let)^{W}. For the extended type inference algorithm we need to allow for the use of the rules (Simp) and (Imp) for simplification and improvement, respectively, at arbitrary points in the derivation. It is sufficient to show that each of the these rules preserves the conclusions of the auxiliary result in the second part. More precisely, suppose that Q | TA′ ⊢^{W} E : ν and that there is a substitution R such that:

RT ≈ S, P_{0} ⊢⊢ RQ, and S′τ = Rν.

As an aside, note that, for any simplifying function simp and any improving function impr, neither simp Q or impr Q can fail since ⌊Q⌋_{P_{0}} ≠ ∅. (See Section 6.) There are two cases to consider:

Case (Simp): We have a type inference derivation of the form:

Q |TA ⊢^{W} E : ν P ⇔ Q
P |TA ⊢^{W} E : ν

From P ⇔ Q, it follows that Q ⊢⊢ P, and hence P_{0} ⊢⊢ RP as required.

Case (Imp): We have a type inference derivation of the form:

Q |TA ⊢^{W} E : ν T′ improves Q
T′Q |T′TA ⊢^{W} E : T′ν

Since P_{0} ⊢⊢ RQ and T′ improves Q, it follows that RQ ∈ ⌊Q⌋_{P_{0}} = ⌊T′Q⌋_{P_{0}} and hence RQ = R′T′Q for some substitution R′. Let V be the set of new variables involved in T′; the only other variables involved in T′ are members of TV (Q). Now consider the substitution R′′ using:

R′′α = R′α, if α ∈ TV (Q) ∪ V
= Rα, otherwise

We claim that R′′T′ ≈ R. To see this, note that:

• If α ∈ TV (Q), then R′′(T′α) = R′(T′α) = Rα.

• If α ∉ TV (Q) ∪ V , then R′′(T′α) = R′′α = Rα.

It follows that R′′T′T ≈ RT ≈ S, P_{0} ⊢⊢ RQ = R′′(T′Q), and S′τ = Rν = R′′(T′ν) as required.

This completes the proof. □

Lemma 6 Suppose that:

∀S′.(P_{0} ⊢⊢ S′P ∧ S′A = A) ⇒ ∃R.(RT ≈ S ∧ P_{0} ⊢⊢ RQ ∧ S′τ = Rν).

Then there is a substitution R such that RT ≈ S and:

Gen(A, P ⇒ τ) ≤_{P_{0}}^{sat} RGen(TA′, Q ⇒ ν).

For convenience, we define:

σ = Gen(A, P ⇒ τ) = ∀α_{i}.P ⇒ τ
σ′ = Gen(TA′, Q ⇒ ν) = ∀β_{j} .Q ⇒ ν.

Our goal is to find a substitution R such that σ ≤_{P_{0}}^{sat} Rσ′. Suppose that [τ_{i}/α_{i}]τ ∈ [[σ]]_{P_{0}}^{sat}. Hence P_{0} ⊢⊢ [τ_{i}/α_{i}]P. Furthermore, since none of α_{i} appears free in A, we have [τ_{i}/α_{i}]A = A. By hypothesis, there is a substitution R such that:

RT ≈ s, P_{0} ⊢⊢ RQ, and [τ_{i}/α_{i}]τ = Rν.

Pick new variables γ_{j} , so that Rσ′ = ∀γ_{j} .R[γ_{j} /β_{j} ](Q ⇒ ν). Let ν_{j} = Rβ_{j} , and note that [ν_{j} /γ_{j} ] · R[γ_{j} /β_{j} ] = R[Rβ_{j} /β_{j} ] = R. Thus:

P_{0} ⊢⊢ [ν_{j} /γ_{j} ](R[γ_{j} /β_{j} ]Q) and [τ_{i}/α_{i}]τ = [ν_{j} /γ_{j} ](R[γ_{j} /β_{j} ]ν).

It follows that [τ_{i}/α_{i}]τ ∈ [[Rσ′]]_{P_{0}}^{sat}. Thus far, it appears that the choice of R may depend of the initial choice of types τ_{i} for the representative element, [τ_{i}/α_{i}]τ ∈ [[σ]]_{P_{0}}^{sat}. Now suppose that we pick another element, [τ′_{i}/α_{i}]τ ∈ [[R′σ′]]_{P_{0}}^{sat}, where R′T ≈ S ≈ RT . The only free variables in σ′ are also free in TA′. But R(TA′) = SA′ = R′(TA′), so R and R′ agree on the free variables of σ′. Thus Rσ′ = R′σ′, and:

[[σ]]_{P_{0}}^{sat} ⊆ [[Rσ′]]_{P_{0}}^{sat}

for some fixed substitution R such that RT ≈ S. □

References

[1] K. Chen, P. Hudak, and M. Odersky. Parametric type classes (extended abstract). In ACM conference on LISP and Functional Programming, San Francisco, CA, June 1992.

[2] Kung Chen. A parametric extension of Haskell’s type classes. PhD thesis, Yale University, Department of Computer Science, 1994 (forthcoming).

[3] D. Clément, J. Despeyroux, T. Despeyroux, and G. Kahn. A simple applicative language: Mini-ML. In ACM symposium on LISP and Functional Programming, Cambridge, Massachusetts, August 1986.

[4] Y.-C. Fuh and P. Mishra. Polymorphic subtype inference: Closing the theorypractice gap. In Proceedings of TAPSOFT 89, New York, 1989. Springer-Verlag. Lecture Notes in Computer Science, 352.

[5] Y.-C. Fuh and P. Mishra. Type inference with subtypes. Theoretical computer science, 73, 1990.

[6] Robert Harper and Benjamin Pierce. A record calculus based on symmetric concatenation. In Conference record of the Eighteenth Annual ACM Symposium on Principles of Programming Languages, Orlando, FL, January 1991.

[7] R.W. Harper and B.C. Pierce. Extensible records without subsumption. Technical report CMU-CS-90-102, Carnegie Mellon University, School of computer science, February 1990.

[8] P. Hudak, S. Peyton Jones, and P. Wadler (editors). Report on the Programming Language Haskell, A Non-strict Purely Functional Language (Version 1.2). ACM SIGPLAN Notices, 27(5), May 1992.

[9] Mark P. Jones. Introduction to Gofer 2.20, September 1991. Available by anonymous ftp from nebula.cs.yale.edu in the directory pub/haskell/gofer as part of the standard Gofer distribution.

[10] Mark P. Jones. Type inference for qualified types. Technical Report PRG-TR-10-91, Programming Research Group, Oxford University Computing Laboratory, Oxford, England, May 1991. Largely superceded by [12].

[11] Mark P. Jones. Computing with lattices: An application of type classes. Journal of Functional Programming, 2(4), October 1992.

[12] Mark P. Jones. Qualified Types: Theory and Practice. PhD thesis, Programming Research Group, Oxford University Computing Laboratory, July 1992. To be published by Cambridge University Press.

[13] Mark P. Jones. A theory of qualified types. In ESOP ’92: European Symposium on Programming, Rennes, France, New York, February 1992. Springer-Verlag. Lecture Notes in Computer Science, 582.

[14] Mark P. Jones. Coherence for qualified types. Research Report YALEU/DCS/RR-989, Yale University, New Haven, Connecticut, USA, September 1993.

[15] Mark P. Jones. A system of constructor classes: overloading and implicit higher-order polymorphism. In FPCA ’93: Conference on Functional Programming Languages and Computer Architecture, Copenhagen, Denmark, New York, June 1993. ACM Press.

[16] Mark P. Jones. The implementation of the Gofer functional programming system. Research Report YALEU/DCS/RR-1030, Yale University, New Haven, Connecticut, USA, May 1994.

[17] J.C. Mitchell. Coercion and type inference (summary). In Conference record of the Eleventh Annual ACM Symposium on Principles of Programming Languages, Salt Lake City, Utah, January 1984.

[18] J.C. Mitchell. Type inference with simple subtypes. Journal of functional programming, 1(3):245–286, July 1991.

[19] Didier Rémy. Typechecking records and variants in a natural extension of ML. In Conference record of the Sixteenth Annual ACM Symposium on Principles of Programming Languages, Austin, TX, January 1989.

[20] Geoffrey Seward Smith. Polymorphic type inference for languages with overloading and subtyping. PhD thesis, Department of Computer Science, Cornell University, Ithaca, New York, August 1991.

[21] D. Volpano and G. Smith. On the complexity of ML typability with overloading. In 5th ACM conference on Functional Programming Languages and Computer Architecture, New York, 1991. Springer-Verlag. Lecture Notes in Computer Science, 523.

[22] P. Wadler and S. Blott. How to make ad hoc polymorphism less ad hoc. In Proceedings of 16th ACM Symposium on Principles of Programming Languages, pages 60–76, Jan 1989.