Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
12 views17 pages

Wadler

This paper introduces type classes as a solution to ad-hoc polymorphism, allowing for operator overloading in a way that extends the Hindley/Milner type system. Type classes enable the definition of functions that can operate on multiple types while maintaining type safety, addressing limitations seen in languages like Standard ML and Miranda. The authors provide informal examples and formal rules to illustrate how type classes can be implemented and utilized in programming languages.

Uploaded by

recibirdocs
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views17 pages

Wadler

This paper introduces type classes as a solution to ad-hoc polymorphism, allowing for operator overloading in a way that extends the Hindley/Milner type system. Type classes enable the definition of functions that can operate on multiple types while maintaining type safety, addressing limitations seen in languages like Standard ML and Miranda. The authors provide informal examples and formal rules to illustrate how type classes can be implemented and utilized in programming languages.

Uploaded by

recibirdocs
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

How to make ad-hoc polymorphism less ad hoc

Philip Wadler and Stephen Blott


University of Glasgow∗
October 1988

Abstract integers and a list of floating point numbers.


One widely accepted approach to parametric
This paper presents type classes, a new approach polymorphism is the Hindley/Milner type system
to ad-hoc polymorphism. Type classes permit over- [Hin69, Mil78, DM82], which is used in Standard
loading of arithmetic operators such as multiplica- ML [HMM86, Mil87], Miranda1 [Tur85], and other
tion, and generalise the “eqtype variables” of Stan- languages. On the other hand, there is no widely
dard ML. Type classes extend the Hindley/Milner accepted approach to ad-hoc polymorphism, and so
polymorphic type system, and provide a new ap- its name is doubly appropriate.
proach to issues that arise in object-oriented pro- This paper presents type classes, which extend the
gramming, bounded type quantification, and ab- Hindley/Milner type system to include certain kinds
stract data types. This paper provides an informal of overloading, and thus bring together the two sorts
introduction to type classes, and defines them for- of polymorphism that Strachey separated.
mally by means of type inference rules. The type system presented here is a generalisa-
tion of the Hindley/Milner type system. As in that
system, type declarations can be inferred, so explicit
1 Introduction type declarations for functions are not required. Dur-
ing the inference process, it is possible to translate a
Strachey chose the adjectives ad-hoc and parametric program using type classes to an equivalent program
to distinguish two varieties of polymorphism [Str67]. that does not use overloading. The translated pro-
Ad-hoc polymorphism occurs when a function is grams are typable in the (ungeneralised) Hindley/
defined over several different types, acting in a dif- Milner type system.
ferent way for each type. A typical example is The body of this paper gives an informal introduc-
overloaded multiplication: the same symbol may be tion to type classes and the translation rules, while
used to denote multiplication of integers (as in 3*3) an appendix gives formal rules for typing and trans-
and multiplication of floating point values (as in lation, in the form of inference rules (as in [DM82]).
3.14*3.14). The translation rules provide a semantics for type
Parametric polymorphism occurs when a function classes. They also provide one possible implementa-
is defined over a range of types, acting in the same tion technique: if desired, the new system could be
way for each type. A typical example is the length added to an existing language with Hindley/Milner
function, which acts in the same way on a list of types simply by writing a pre-processor.
∗ Authors’ address: Department of Computing Science, Two places where the issues of ad-hoc polymor-
University of Glasgow, Glasgow G12 8QQ, Scotland. Elec- phism arise are the definition of operators for arith-
tronic mail: wadler, [email protected] . metic and equality. Below we examine the ap-
Published in: 16’th ACM Symposium on Principles of Pro- proaches to these three problems adopted by Stan-
gramming Languages, Austin, Texas, January 1989. dard ML and Miranda; not only do the approaches
Permission to copy without fee all or part of this material is differ between the two languages, they also differ
granted provided that the copies are not made or distributed within a single language. But as we shall see, type
for direct commercial advantage, the ACM copyright notice
classes provide a uniform mechanism that can ad-
and the title of the publication and its date appear, and no-
tice is given that copying is by permission of the Association dress these problems.
for Computing Machinery. To copy otherwise, or to republish,
requires a fee and/or specific permission. 1 Miranda is a trademark of Research Software Limited.

1
This work grew out of the efforts of the Haskell arithmetic and equality in Standard ML and Mi-
committee to design a lazy functional programming randa.
language2 . One of the goals of the Haskell commit- Arithmetic. In the simplest approach to overload-
tee was to adopt “off the shelf” solutions to problems ing, basic operations such as addition and multiplica-
wherever possible. We were a little surprised to re- tion are overloaded, but functions defined in terms of
alise that arithmetic and equality were areas where them are not. For example, although one can write
no standard solution was available! Type classes 3*3 and 3.14*3.14, one cannot define
were developed as an attempt to find a better so-
lution to these problems; the solution was judged square x = x*x
successful enough to be included in the Haskell de- and then write terms such as
sign. However, type classes should be judged inde-
pendently of Haskell; they could just as well be in- square 3
corporated into another language, such as Standard square 3.14
ML.
This is the approach taken in Standard ML. (Inci-
Type classes appear to be closely related to issues dentally, it is interesting to note that although Stan-
that arise in object-oriented programming, bounded dard ML includes overloading of arithmetic opera-
quantification of types, and abstract data types tors, its formal definition is deliberately ambiguous
[CW85, MP85, Rey85]. Some of the connections are about how this overloading is resolved [HMT88, page
outlined below, but more work is required to under- 71], and different versions of Standard ML resolve
stand these relations fully. overloading in different ways.)
A type system very similar to ours has been dis- A more general approach is to allow the above
covered independently by Stefan Kaes [Kae88]. Our equation to stand for the definition of two over-
work improves on Kaes’ in several ways, notably loaded versions of square, with types Int -> Int
by the introduction of type classes to group re- and Float -> Float. But consider the function:
lated operators, and by providing a better transla-
tion method. squares (x, y, z)
This paper is divided into two parts: the body = (square x, square y, square z)
gives an informal introduction to type classes, while Since each of x, y, and z might, independently, have
the appendix gives a more formal description. Sec- either type Int or type Float, there are eight possi-
tion 2 motivates the new system by describing limi- ble overloaded versions of this function. In general,
tations of ad-hoc polymorphism as it is used in Stan- there may be exponential growth in the number of
dard ML and Miranda. Section 3 introduces type translations, and this is one reason why such solu-
classes by means of a simple example. Section 4 tions are not widely used.
illustrates how the example of Section 3 may be In Miranda, this problem is side-stepped by not
translated into an equivalent program without type overloading arithmetic operations. Miranda provides
classes. Section 5 presents a second example, the def- only the floating point type (named “num”), and
inition of an overloaded equality function. Section 6 there is no way to use the type system to indicate
describes subclasses. Section 7 discusses related work that an operation is restricted to integers.
and concludes. Appendix A presents inference rules
for typing and translation. Equality. The history of the equality operation is
checkered: it has been treated as overloaded, fully
polymorphic, and partly polymorphic.
2 Limitations of ad-hoc The first approach to equality is to make it over-
loaded, just like multiplication. In particular, equal-
polymorphism ity may be overloaded on every monotype that ad-
mits equality, i.e., does not contain an abstract type
This section motivates our treatment of ad-hoc poly- or a function type. In such a language, one may
morphism, by examining problems that arise with write 3*4 == 12 to denote equality over integers, or
2 The Haskell committee includes: Arvind, Brian Boutel, ’a’ == ’b’ to denote equality over characters. But
Jon Fairbairn, Joe Fasel, Paul Hudak, John Hughes, Thomas one cannot define a function member by the equations
Johnsson, Dick Kieburtz, Simon Peyton Jones, Rishiyur
Nikhil, Mike Reeve, Philip Wadler, David Wise, and Jonathan member [] y = False
Young. member (x:xs) y = (x == y) \/ member xs y

2
and then write terms such as dictionary of appropriate methods. This is exactly
the approach used in object-oriented programming
member [1,2,3] 2 [GR83].
member "Haskell" ’k’ In the case of polymorphic equality, this means
(We abbreviate a list of characters [’a’,’b’,’c’] that both arguments of the equality function will
as "abc".) This is the approach taken in the first contain a pointer to the same dictionary (since they
version of Standard ML [Mil84]. are both of the same type). This suggests that per-
A second approach is to make equality fully poly- haps dictionaries should be passed around indepen-
morphic. In this case, its type is dently of objects; now polymorphic equality would
be passed one dictionary and two objects (minus dic-
(==) :: a -> a -> Bool tionaries). This is the intuition behind type classes
and the translation method described here.
where a is a type variable ranging over every type.
The type of the member function is now

member :: [a] -> a -> Bool 3 An introductory example


(We write [a] for the type “list of a”.) This means We will now introduce type classes by means of an
that applying equality to functions or abstract types example.
does not generate a type error. This is the approach Say that we wish to overload (+), (*), and negate
taken in Miranda: if equality is applied on a func- (unary minus) on types Int and Float. To do so, we
tion type, the result is a run-time error; if equality is introduce a new type class, called Num, as shown in
applied on an abstract type, the result is to test the the class declaration in Figure 1. This declaration
representation for equality. This last may be consid- may be read as stating “a type a belongs to class Num
ered a bug, as it violates the principle of abstraction. if there are functions named (+), (*), and negate,
A third approach is to make equality polymorphic of the appropriate types, defined on it.”
in a limited way. In this case, its type is We may now declare instances of this class, as
shown by the two instance declarations in Figure 1.
(==) :: a(==) -> a(==) -> Bool
The assertion Num Int may be read “there are func-
where a(==) is a type variable ranging only over tions named (+), (*), and negate, of the appropri-
types that admit equality. The type of the member ate types, defined on Int”. The instance declaration
function is now justifies this assertion by giving appropriate bindings
for the three functions. The type inference algorithm
member :: [a(==) ] -> a(==) -> Bool must verify that these bindings do have the appropri-
ate type, i.e., that addInt has type Int->Int->Int,
Applying equality, or member, on a function type or and similarly for mulInt and negInt. (We assume
abstract type is now a type error. This is the ap- that addInt, mulInt, and negInt are defined in the
proach currently taken in Standard ML, where a(==) standard prelude.) The instance Num Float is de-
is written ’’a, and called an “eqtype variable”. clared similarly.
Polymorphic equality places certain demands upon
A word on notational conventions: Type class
the implementor of the run-time system. For in-
names and type constructor names begin with a capi-
stance, in Standard ML reference types are tested
tal letter, and type variable names begin with a small
for equality differently from other types, so it must
letter. Here, Num is a type class, Int and Float are
be possible at run-time to distinguish references from
type constructors, and a is a type variable.
other pointers.
We may now define
Object-oriented programming. It would be nice
if polymorphic equality could be extended to include square x = x * x
user-defined equality operations over abstract types.
To implement this, we would need to require that There exists an algorithm that can infer the type
every object carry with it a pointer to a method, a of square from this definition (it is outlined in the
procedure for performing the equality test. If we are appendix). It derives the type:
to have more than one operation with this property,
then each object should carry with it a pointer to a square :: Num a => a -> a

3
class Num a where
(+), (*) :: a -> a -> a
negate :: a -> a
instance Num Int where
(+) = addInt
(*) = mulInt
negate = negInt
instance Num Float where
(+) = addFloat
(*) = mulFloat
negate = negFloat
square :: Num a => a -> a
square x = x * x
squares :: Num a, Num b, Num c => (a,b,c) -> (a,b,c)
squares (x, y, z) = (square x, square y, square z)

Figure 1: Definition of arithmetic operations

data NumD a = NumDict (a -> a -> a) (a -> a -> a) (a -> a)


add (NumDict a m n) = a
mul (NumDict a m n) = m
neg (NumDict a m n) = n
numDInt :: NumD Int
numDInt = NumDict addInt mulInt negInt
numDFloat :: NumD Float
numDFloat = NumDict addFloat mulFloat negFloat
square’ :: NumD a -> a -> a
square’ numDa x = mul numDa x x
squares’ :: (NumD a, NumD b, NumD c) -> (a,b,c) -> (a,b,c)
squares’ (numDa, numDb, numDc) (x, y, z)
= (square’ numDa x, square’ numDb y, square’ numDc z)

Figure 2: Translation of arithmetic operations

4
This is read, “square has type a -> a, for every a Each term of the form x+y, x*y, and negate x is
such that a belongs to class Num (i.e., such that (+), now replaced by a corresponding term, as follows:
(*), and negate are defined on a).” We can now
x+y --> add numD x y
write terms such as
x*y --> mul numD x y
square 3 negate x --> neg numD x
square 3.14 where numD is an appropriate dictionary. How is the
and an appropriate type will be derived for each (Int appropriate dictionary determined? By its type. For
for the first expression, Float for the second). On example, we have the following translations:
the other hand, writing square ’x’ will yield a type 3 * 3
error at compile time, because Char has not been --> mul numDInt 3 3
asserted (via an instance declaration) to be a numeric
type. 3.14 * 3.14
Finally, if we define the function squares men- --> mul numDFloat 3.14 3.14
tioned previously, then the type given in Figure 1 As an optimisation, it is easy for the compiler to
will be inferred. This type may be read, “squares perform beta reductions to transform these into
has the type (a,b,c) -> (a,b,c) for every a, b, mulInt 3 3 and mulFloat 3.14 3.14, respectively.
and c such that a, b, and c belong to class Num”. If the type of a function contains a class, then this
(We write (a,b,c) for the type that is the cartesian is translated into a dictionary that is passed at run-
product of a, b, and c.) So squares has one type, time. For example, here is the definition of square
not eight. Terms such as with its type
squares (1, 2, 3.14) square :: Num a => a -> a
square x = x * x
are legal, and derive an appropriate type.
This translates to

4 Translation square’ :: NumD a -> a -> a


square’ numD x = mul numD x x
One feature of this form of overloading is that it Each application of square must be translated to
is possible at compile-time to translate any pro- pass in the appropriate extra parameter:
gram containing class and instance declarations to
an equivalent program that does not. The equiva- square 3
lent program will have a valid Hindley/Milner type. --> square’ numDInt 3
The translation method will be illustrated by square 3.2
means of an example. Figure 2 shows the transla- --> square’ numDFloat 3
tion of the declarations in Figure 1.
Finally, the translation of squares is also shown
For each class declaration we introduce a new
in Figure 2. Just as there is one type, rather than
type, corresponding to an appropriate “method dic-
eight, there is only one translation, rather than eight.
tionary” for that class, and functions to access the
Exponential growth is avoided.
methods in the dictionary. In this case, correspond-
ing to the class Num we introduce the type NumD as
shown in Figure 2. The data declaration defines 5 A further example: equality
NumD to be a type constructor for a new type. Values
of this type are created using the value constructor This section shows how to define equality using class
NumDict, and have three components of the types and instance declarations. Type classes serve as a
shown. The functions add, mul, and neg take a value straightforward generalisation of the “eqtype vari-
of type NumD and return its first, second, and third ables” used in Standard ML. Unlike Standard ML,
component, respectively. this mechanism allows the user to extend equality
Each instance of the class Num is translated into over abstract types in a straightforward way. And,
the declaration of a value of type NumD. Thus, corre- unlike Standard ML, this mechanism can be trans-
sponding to the instance Num Int we declare a data lated out at compile time, so it places no special de-
structure of type NumD Int, and similarly for Float. mands on the implementor of the run-time system.

5
class Eq a where
(==) :: a -> a -> bool
instance Eq Int where
(==) = eqInt
instance Eq Char where
(==) = eqChar
member :: Eq a => [a] -> a -> Bool
member [] y = False
member (x:xs) y = (x == y) \/ member xs y
instance Eq a, Eq b => Eq (a,b) where
(u,v) == (x,y) = (u == x) & (v == y)
instance Eq a => Eq [a] where
[] == [] = True
[] == y:ys = False
x:xs == [] = False
x:xs == y:ys = (x == y) & (xs == ys)
data Set a = MkSet [a]
instance Eq a => Eq (Set a) where
MkSet xs == MkSet ys = and (map (member xs) ys)
& and (map (member ys) xs)

Figure 3: Definition of equality

The definition is summarised in Figure 3. We be- a and b such that a is in class Eq and b is in class Eq,
gin by declaring a class, Eq, containing a single op- the pair (a,b) is also in class Eq.” In other words,
erator, (==), and instances Eq Int and Eq Char of “if equality is defined on a and equality is defined on
this class. b, then equality is defined on (a,b).” The instance
We then define the member function in the usual defines equality on pairs in terms of equality on the
way, as shown in Figure 3. The type of member need two components, in the usual way.
not be given explicitly, as it can be inferred. The Similarly, it is possible to define equality over lists.
inferred type is: The first line of this instance reads, “if equality is
defined on a, then equality is defined on type ‘list of
member :: Eq a => [a] -> a -> Bool a’.” We may now write terms such as
This is read “member has type [a] -> a -> Bool, "hello" == "goodbye"
for every type a such that a is in class Eq [[1,2,3],[4,5,6]] == []
(i.e., such that equality is defined on a)” (This member ["Haskell", "Alonzo"] "Moses"
is exactly equivalent to the Standard ML type
which all evaluate to False.
’’a list->’’a->bool, where ’’a is an “eqtype
The final data declaration defines a new type con-
variable”.) We may now write terms such as
structor Set and a new value constructor MkSet. If
member [1,2,3] 2 a module exports Set but hides MkSet, then out-
member "Haskell" ’k’ side of the module the representation of Set will not
be accessible; this is the mechanism used in Haskell
which both evaluate to True. to define abstract data types. The final instance de-
Next, we give an instance defining equality over fines equality over sets. The first line of this instance
pairs. The first line of this instance reads, “for every reads, “if equality is defined on a, then equality is

6
data EqD a = EqDict (a -> a -> Bool)
eq (EqDict e) = e
eqDInt :: EqD Int
eqDInt = EqDict eqInt
eqDChar :: EqD Int
eqDChar = EqDict eqChar
member’ :: EqD a -> [a] -> a -> Bool
member’ eqDa [] y = False
member’ eqDa (x:xs) y = eq eqDa x y \/ member’ eqDa xs y
eqDPair :: (EqD a, EqD b) -> EqD (a,b)
eqDPair (eqDa,eqDb) = EqDict (eqPair (eqDa,eqDb))
eqPair :: (EqD a, EqD b) -> (a,b) -> (a,b) -> Bool
eqPair (eqDa,eqDb) (x,y) (u,v) = eq eqDa x u & eq eqDb y v
eqDList :: EqD a -> EqD [a]
eqDList eqDa = EqDict (eqList eqDa)
eqList :: EqD a -> [a] -> [a] -> Bool
eqList eqDa [] [] = True
eqList eqDa [] (y:ys) = False
eqList eqDa (x:xs) [] = False
eqList eqDa (x:xs) (y:ys) = eq eqDa x y & eq (eqDList eqDa) xs ys

Figure 4: Translation of equality

defined on type ‘set of a’.” In this case, sets are rep- in Figure 3. The first part of the translation intro-
resented in terms of lists, and two sets are taken to duces nothing new, and is similar to the translation
be equal if every member of the first is a member in Section 4.
of the second, and vice-versa. (The definition uses We begin by defining a dicitionary EqD correspond-
standard functions map, which applies a function to ing to the class Eq. In this case, the class contains
every element of a list, and and, which returns the only one operation, (==), so the dictionary has only
conjunction of a list of booleans.) Because set equal- one entry. The selector function eq takes a dictio-
ity is defined in terms of member, and member uses nary of type EqD a and returns the one entry, of
overloaded equality, it is valid to apply equality to type a->a->Bool. Corresponding to the instances
sets of integers, sets of lists of integers, and even sets Eq Int and Eq Char we define two dictionaries of
of sets of integers. types EqD Int and EqD Char, containing the appro-
This last example shows how the type class mech- priate equality functions, and the function member
anism allows overloaded functions to be defined over is translated to member’ in a straightforward way.
abstract data types in a natural way. In particular, Here are three terms and their translations:
this provides an improvement over the treatment of 3*4 == 12
equality provided in Standard ML or Miranda. --> eq eqDInt (mul numDInt 3 4) 12
member [1,2,3] 2
5.1 Translation of equality --> member’ eqDInt [1,2,3] 2

We now consider how the translation mechanism ap- member "Haskell" ’k’
plies to the equality example. --> member’ eqDChar "Haskell" ’k’
Figure 4 shows the translation of the declarations The translation of the instance declaration for

7
equality over lists is a little trickier. Recall that the numerical and equality operations, then these each
instance declaration begins appear in the type separately:

instance Eq a => Eq [a] where memsq :: Eq a, Num a => [a]->a->Bool


... memsq xs x = member xs (square x)
This states that equality is defined over type [a] if As a practical matter, this seems a bit odd—we
equality is defined over type a. Corresponding to would expect every data type that has (+), (*), and
this, the instance dictionary for type [a] is param- negate defined on it to have (==) defined as well; but
eterised by a dictionary for type a, and so has the not the converse. Thus it seems sensible to make Num
type a subclass of Eq.
We can do this as follows:
eqDList :: EqD a -> EqD [a]
class Eq a => Num a where
The remainder of the translation is shown in Figure (+) :: a -> a -> a
4, as is the translation for equality over pairs. Here (*) :: a -> a -> a
are three terms and their translations: negate :: a -> a
"hello" == "goodbye"
This asserts that a may belong to class Num only if
--> eq (eqDList eqDChar)
it also belongs to class Eq. In other words, Num is a
"hello"
subclass of Eq, or, equivalently, Eq is a superclass of
"goodbye"
Num. The instance declarations remain the same as
[[1,2,3],[4,5,6]] == [] before—but the instance declaration Num Int is only
--> eq (eqDList (eqDList eqDInt)) valid if there is also an instance declaration Eq Int
[[1,2,3],[4,5,6]] active within the same scope.
[] From this it follows that whenever a type contains
member ["Haskell", "Alonzo"] "Moses" Num a it must also contain Eq a; therefore as a con-
--> member’ (eqDList eqDChar) venient abbreviation we permit Eq a to be omitted
["Haskell", "Alonzo"] from a type whenever Num a is present. Thus, for
"Moses" the type of memsq we could now write

As an optimisation, it is easy for the compiler to per- memsq :: Num a => [a]->a->Bool
form beta reductions to transform terms of the form
The qualifier Eq a no longer needs to be mentioned,
eq (eqDList eqD) into eqList eqD, where eqD is
because it is implied by Num a.
any dictionary for equality. This optimisation may
In general, each class may have any number of sub
be applied to the first two examples above, and also
or superclasses. Here is a contrived example:
to the definition of eqList itself in Figure 4.
It is worthwhile to compare the efficiency of this class Top a where
translation technique with polymorphic equality as fun1 :: a -> a
found in Standard ML or Miranda. The individual
class Top a => Left a where
operations, such as eqInt are slightly more efficient
fun2 :: a -> a
than polymorphic equality, because the type of the
argument is known in advance. On the other hand, class Top a => Right a where
operations such as member and eqList must explic- fun3 :: a -> a
itly pass an equality operation around, an overhead class Left a, Right a => Bottom a
that polymorphic equality avoids. Further experi- where
ence is needed to asses the trade-off between these fun4 :: a -> a
costs.
The relationships among these types can be dia-
grammed as follows:
6 Subclasses
Top
In the preceeding, Num and Eq were considered as / \
completely separate classes. If we want to use both / \

8
Left Right class Coerce a b where
\ / coerce :: a -> b
\ / instance Coerce Int Float where
Bottom coerce = convertIntToFloat
Although multiple superclasses pose some prob-
In this case, the assertion Coerce a b might be
lems for the usual means of implementing object-
taken as equivalent to the assertion that a is a sub-
oriented languages, they pose no problems for the
type of b. This suggests a relation between this work
translation scheme outlined here. The translation
and work on bounded quantification and on subtypes
simply assures that the appropriate dictionaries are
(see [CW85, Rey85] for excellent surveys of work in
passed at run-time; no special hashing schemes are
this area, and [Wan87, Car88] for more recent work).
required, as in some object-oriented systems.
Type classes may be thought of as a kind of
bounded quantifier, limiting the types that a type
7 Conclusion variable may instantiate to. But unlike other ap-
proaches to bounded quantification, type classes do
It is natural to think of adding assertions to the class not introduce any implicit coercions (such as from
declaration, specifying properties that each instance subtype Int to supertype Float, or from a record
must satisfy: with fields x, y, and z to a record with fields x and
y). Further exploration of the relationship between
class Eq a where type classes and these other approaches is likely to
(==) :: a -> a -> Bool be fruitful.
% (==) is an equivalence relation Type classes also may be thought of as a kind
class Num a where of abstract data type. Each type class specifies
zero, one :: a a collection of functions and their types, but not
(+), (*) :: a -> a -> a how they are to be implemented. In a way, each
negate :: a -> a type class corresponds to an abstract data type with
% (zero, one, (+), (*), negate) many implementations, one for each instance dec-
% form a ring laration. Again, exploration of the relationship be-
tween type classes and current work on abstract data
It is valid for any proof to rely on these properties, so types [CW85, MP85, Rey85] appears to be called for.
long as one proves that they hold for each instance We have already referred to the work of Kaes. One
declaration. Here the assertions have simply been advance of our work over his is the conceptual and
written as comments; a more sophisticated system notational benefit of grouping overloaded functions
could perhaps verify or use such assertions. This sug- into classes. In addition, our system is more gen-
gests a relation between classes and object-oriented eral; Kaes cannot handle overloadings involving more
programming of a different sort, since class declara- than one type variable, such as the coerce example
tions now begin to resemble object declarations in above. Finally, our translation rules are an improve-
OBJ [FGJM85]. ment over his. Kaes outlines two sets of translation
It is possible to have overloaded constants, such as rules (which he calls “semantics”), one static and one
zero and one in the above example. However, unre- dynamic. His dynamic semantics is more limited in
stricted overloading of constants leads to situations power than the language described here; his static
where the overloading cannot be resolved without semantics appears similar in power, but, unlike the
providing extra type information. For instance, the translation described here, can greatly increase the
expression one * one is meaningless unless it is used size of a program.
in a context that specifies whether its result is an Int One drawback of our translation method is that
or a Float. For this reason, we have been careful in it introduces new parameters to be passed at run-
this paper to use constants that are not overloaded: time, corresponding to method dictionaries. It may
3 has type Int, and 3.14 has type Float. A more be possible to eliminate some of these costs by us-
general treatment of constants seems to require co- ing partial evaluation [BEJ88] to generate versions
ercion between subtypes. of functions specialised for certain dictionaries; this
It is reasonable to allow a class to apply to more would reduce run time at the cost of increasing code
than one type variable. For instance, we might have size. Further work is needed to assess the trade-offs

9
between our approach (with or without partial eval- types in other expressions will be inferred by the
uation) and other techniques. rules given here.
It is clear from the above that many issues remain As an example, a portion of the definition of equal-
to be explored, and many tradeoffs remain to be as- ity given in Figure 3 is shown in Figure 6. In this
sessed. We look forward to the practical experience figure, and in the rest of this appendix, we use Eq τ
with type classes that Haskell will provide. as an abbreviation for the type τ → τ → Bool .
Acknowledgements. The important idea that As a second example, a portion of the definition
overloading might be reflected in the type of a func- of arithmetic operators given in Figure 1 is shown in
tion was suggested (in a rather different form) by Figure 7. In this figure we use Num τ as an abbrevi-
Joe Fasel. For discussion and comments, we are also ation for the type
grateful to: Luca Cardelli, Bob Harper, Paul Hudak, (τ → τ → τ, τ → τ → τ, τ → τ )
John Hughes, Stefan Kaes, John Launchbury, John
Mitchell, Kevin Mitchell, Nick Rothwell, Mads Tofte, In translating to the formal language, we have
David Watt, the members of the Haskell committee, grouped the three operators together into a “dictio-
and the members of IFIP 2.8. nary”. This is straightforward, and independent of
the central issue: how to resolve overloading.

A Typing and translation


A.2 Types
rules
The Damas/Milner system distinguishes between
This appendix presents the formal typing and trans- types (written τ ) and type schemes (written σ). Our
lation rules, one set of rules performing both typing system adds a third syntactic group, predicated types.
and translation. The rules are an extension of those The syntax of these is given in Figure 5.
given by Damas and Milner [DM82]. In the full language, we wrote types such as

member :: Eq a => [a] -> a -> Bool


A.1 Language
In the simplified language, we write this in the form
To present the typing and translation rules for over-
loading, it is helpful to use a slightly simpler language member :: ∀α. (eq :: Eq α). [α] → α → Bool
that captures the essential issues. We will use a lan-
guage with the usual constructs (identifiers, appli- The restriction Eq a can be read “equality is defined
cations, lambda abstractions, and let expressions), on type a” and the corresponding restriction (eq ::
plus two new constructs, over and inst expressions, Eq α) can be read “eq must have an instance of type
that correspond to class and instance declarations, Eq α”.
respectively. The syntax of expressions and types is In general, we refer to (x :: τ ). ρ as a predicated
given in Figure 5. type and (x :: τ ) as a predicate.
An over expression We will give rules for deriving typings of the form

over x :: σ in e A ⊢ e :: σ \ e

declares x to be an overloaded identifier. Within the This can be read as, “under the set of assumptions
scope of this declaration, there may be one or more A, the expression e has well-typing σ with transla-
corresponding inst expressions tion e”. Each typing also includes a translation, so
the rules derive typing\translation pairs. It is possi-
inst x :: σ ′ = e0 in e1 ble to present the typing rules without reference to
the translation, simply by deleting the ‘\e’ portion
where the type σ ′ is an instance of the type σ (a from all rules. It is not, however, possible to present
notion to be made precise later). Unlike lambda the translation rules independently, since typing con-
and let expressions, the bound variables in over and trols the translation. For example, the introduction
inst expressions may not be redeclared in a smaller and elimination of predicates in types controls the
scope. Also unlike lambda and let expressions, over introduction and elimination of lambda abstractions
and inst expressions must contain explicit types; the in translations.

10
Identifiers x
Expressions e ::= x
| e0 e1
| λx. e
| let x = e0 in e1
| over x :: σ in e
| inst x :: σ = e0 in e1
Type Variables α
Type Constructors χ
Types τ ::= (τ → τ ′ ) | α | χ(τ1 . . . τn )
Predicated Types ρ ::= (x :: τ ). ρ | τ
Type-schemes σ ::= ∀α. σ | ρ

Figure 5: Syntax of expressions and types

over eq :: ∀α. Eq α in
inst eq :: Eq Int = eqInt in
inst eq :: Eq Char = eqChar in
inst eq :: ∀α.∀β.(eq :: Eq α).(eq :: Eq β).Eq (α, β)
= λp.λq. eq (fst p) (fst q) ∧ eq (snd p) (snd q) in
eq (1, ‘a’) (2, ‘b’)

Figure 6: Definition of equality, formalised

over numD :: ∀α. Num α in


inst numD :: Num Int = (addInt , mulInt , negInt) in
inst numD :: Num Float = (addFloat , mulFloat , negFloat ) in
let (+) = fst numD in
let (∗) = snd numD in
let negate = thd numD in
let square = λx. x ∗ x in
square 3

Figure 7: Definition of arithmetic operations, formalised

11
(eq ::o ∀α.Eq α),
(eq ::i Eq Int \ eq (Eq Int ) ),
(eq ::i Eq Char \ eq (Eq Char ) ),
(eq ::i ∀α.∀β.(eq :: Eq α).(eq :: Eq β).Eq (α, β) \ eq (∀α.∀β.(eq::Eq α).(eq::Eq β).Eq (α,β)) ),
(eq :: Eq α \ eq (Eq α) ),
(eq :: Eq β \ eq (Eq β) ),
(p :: (α, β) \ p),
(q :: (α, β) \ q)

Figure 8: Some assumptions

TAUT A, (x :: σ \ x) ⊢ x :: σ \ x

TAUT A, (x ::i σ \ x) ⊢ x :: σ \ x

A ⊢ e :: ∀α. σ \ e
SPEC
A ⊢ e :: [α \ τ ]σ \ e

A ⊢ e :: σ \ e
α not free in A
GEN
A ⊢ e :: ∀α. σ \ e

A ⊢ e :: (τ ′ → τ ) \ e
A ⊢ e′ :: τ ′ \ e′
COMB
A ⊢ (e e′ ) :: τ \ (e e′ )

Ax , (x :: τ ′ \ x) ⊢ e :: τ \ e
ABS
A ⊢ (λx. e) :: (τ ′ → τ ) \ (λx. e)

A ⊢ e :: σ \ e
Ax , (x :: σ \ x) ⊢ e′ :: τ \ e′
LET
A ⊢ (let x = e in e′ ) :: τ \ (let x = e in e′ )

Figure 9: Typing and translation rules, part 1

12
A.3 Assumptions The instance relation
Typing is done in the context of a set of assump-
σ ≥A σ ′
tions, A. The assumptions bind typing and transla-
tion information to the free identifiers in an expres-
where σ = ∀α1 . . . αn . ρ and σ ′ = ∀β1 . . . βm . ρ′ , is
sion. This includes identifiers bound in lambda and
defined as follows:
let expression, and overloaded identifiers. Although
we write them as sequences, assumptions are sets, σ ≥A σ ′ iff
and therefore the order is irrelevant. (1) βi is not free in σ and
There are three forms of binding in an assumption (2) ∃τ1 , . . . , τn . [τ1 /α1 , . . . , τn /αn ]ρ ≥A ρ′
list:
This part is similar to the definition in Damas/
• (x ::o σ) is used for overloaded identifiers;
Milner. The bound variables of σ are specialised and
• (x ::i σ \ xσ ) is used for declared instances of the resulting predicated types are compared.
overloaded identifiers; and Define ρ ≥A ρ′ iff the type part of ρ equals the type
part of ρ′ (the same condition as Damas/Milner),
• (x :: σ \ x) is used for lambda and let bound and for every predicate (x :: τ ) in ρ, either
variables, and assumed instances of overloaded
identifiers. • there is a predicate of the form (x :: τ ) in ρ′ (i.e.
the predicate appears in both types); or
In (x :: σ \ x) and (x ::i σ \ x), the identifier x is the
translation of x. If x is not an overloaded identifier
• the predicate can be eliminated under assump-
(that is, if x is bound by a lambda or let expression),
tions A.
then the assumption for x has the form (x :: σ \ x),
so x simply translates as itself.
A predicate (x :: τ ) can be eliminated under A iff
Figure 8 shows the assumptions available when ap-
either
plying the inference rules to the expression
• (x :: τ \ x) is in A; or
λp. λq. eq (fst p) (fst q) ∧ eq (snd p) (snd q)

in Figure 6. There are three (::i ) bindings, corre- • (x ::i σ ′ \ x) is in A and σ ′ ≥A τ .


sponding to the three instance declarations, and two
For example, if A0 is the set of assumptions in
(::) bindings for the two bound variables, and two
Figure 8, then
(::) bindings corresponding to assumed instances of
equality. (We shall see later how assumed instances
(∀α. (eq :: Eq α). [α] → α → Bool )
are introduced by the PRED rule.)
≥A0 ([Int] → Int → Bool )

A.4 Instances holds. On the other hand,


Given a set of assumptions A, we define an instance
relation between type-schemes, (∀α. (eq :: Eq α). [α] → α → Bool )
≥A0 ([Float ] → Float → Bool )
σ ≥A σ ′ .
does not hold, since A0 contains no binding asserting
This can be read as “σ is more general than σ ′ under that eq has an instance at type Float .
assumptions A”. This is the same as the relationship Two type-schemes are unifiable if they overlap,
defined by Damas and Milner, but extended to apply that is, if there exists a type that is an instance of
to predicated types. both under some set of assumptions. We say that σ
Only certain sets of assumptions are valid. The and σ ′ are unifiable if there exists a type τ and valid
definition of validity depends on the ≥A relation, so set of assumptions A such that
there is a (well-founded) mutual recursion between
the definition of valid assumptions and the definition σ ≥A τ ∧ σ ′ ≥A τ
of ≥A . We give the definition of ≥A in this section,
and the definition of valid assumptions in the next. We write σ#σ ′ if σ and σ ′ are not unifiable.

13
A, (x :: τ \ xτ ) ⊢ e :: ρ \ e
PRED (x ::o σ) ∈ A
A ⊢ e :: (x :: τ ). ρ \ (λxτ . e)

A ⊢ e :: (x :: τ ). ρ \ e
A ⊢ x :: τ \ e′
REL (x ::o σ) ∈ A
A ⊢ e :: ρ \ (e e′ )

Ax , (x ::o σ) ⊢ e :: τ \ e
OVER
A ⊢ (over x :: σ in e) :: τ \ e

A, (x ::i σ ′ \ xσ′ ) ⊢ e′ :: σ ′ \ e′
A, (x ::i σ ′ \ xσ′ ) ⊢ e :: τ \ e
INST (x ::o σ) ∈ A
A ⊢ (inst x :: σ ′ = e′ in e) :: τ \ (let xσ′ = e′ in e)

Figure 10: Typing and translation rules, part 2

let eq (Eq Int ) = eqInt in


let eq (Eq Char ) = eqChar in
let eq (∀α.∀β.(eq::Eq α).(eq::Eq β).Eq (α,β))
= λeq (Eq α) .λeq (Eq β) .λp.λq.
eq (Eq α) (fst p) (fst q) ∧ eq (Eq β) (snd p) (snd q) in
eq (∀α.∀β.(eq::Eq α).(eq::Eq β).Eq (α,β)) eq (Eq Int ) eq (Eq Char ) (1, ‘a’) (2, ‘b’)

Figure 11: Translation of equality, formalised

A1 : (eq ::o ∀α.Eq α)


(eqInt :: Eq Int \ eqInt)
(eqChar :: Eq Int \ eqChar )

e1 : inst eq :: Eq Int = eqInt in


inst eq :: Eq Char = eqChar in
eq

Figure 12: A problematic expression

14
A.5 Valid assumptions For example, let A0 be the set of assumptions
shown in Figure 8, together with assumptions about
All sets of assumptions used within proofs must be
the types of integer and character constants. Then
valid. The valid sets of assumptions are inductively
the above rules are sufficient to derive that
defined as follows:
A0 ⊢ (eq 1 2) :: Bool \ (eq (Eq Int ) 1 2)
• Empty. The empty assumption set, {}, is valid.
A0 ⊢ (eq ‘a’ ‘b’) :: Bool \ (eq (Eq Char ) ‘a’ ‘b’)
• Normal identifier. If A is a valid assumption set,
x is an identifier that does not appear in A, and That is, these rules alone are sufficient to resolve
σ is a type scheme, then simple overloading.
More complicated uses of overloading require the
A, (x :: σ \ x) remaining four rules, shown in Figure 10. The first
two deal with the introduction and elimination of
is a valid assumption set. predicates, and the second two deal with the over
and inst constructs.
• Overloaded identifier. If A is a valid assumption As we have seen, expressions with types that con-
set, x is an identifier that does not appear in A, tain classes (that is, expressions with predicated
σ is a type scheme, and τ1 , . . . , τm are types and types) are translated to lambda abstractions that
σ1 , . . . , σn are types schemes such that require a dictionary to be passed at run-time. This
– σ ≥A σi , for i from 1 to n, and idea is encapsulated in the PRED (“predicate”) and
REL (“release”) rules. The PRED and REL rules
– σ ≥A τi , for i from 1 to m, and introduce and eliminate predicates analogously to
– σi #σj , for distinct i, j from 1 to n the way that the GEN and SPEC rules introduce
and eliminate bound type variables. In particular,
then the PRED rule adds a predicate to a type (and has
a lambda expression as its translation) and the REL
A, (x ::o σ), rule removes a predicate from a type (and has an ap-
(x ::i σ1 \ xσ1 ), . . . , (x ::i σn \ xσn ), plication as its translation).
(x :: τ1 \ xτ1 ), . . . , (x :: τm \ xτm ) The OVER rule types over expressions adding
the appropriate (::o ) binding to the environment,
is a valid assumption set.
and the INST rule types inst expressions adding
For example, the assumptions in Figure 8 are a the appropriate (::i ) binding to the environment.
valid set. However, this set would be invalid if aug- The validity condition on sets of assumptions ensures
mented with the binding that overloaded identifiers are only instanced at valid
types.
(eq ::i ∀γ.Eq (Char , γ) \ eq (∀γ.Eq (Char ,γ)) ) Notice that none of the translations contain over
or inst expressions, therefore, they contain no over-
as this instance overlaps with one already in the set. loading. It is easy to verify that the translations are
themselves well-typed in the Hindley/Milner system.
A.6 Inference rules For example, the program in Figure 6 is translated
by these rules into the program in Figure 11. The
We now give inference rules that characterise well- reader can easily verify that this corresponds to the
typings of the form translation from Figure 3 to Figure 4. We have thus
shown how to formalise the typing and transforma-
A ⊢ e :: σ \ e tion ideas that were presented informally in the body
of the paper.
The rules break into two groups, shown in Figures 9
and 10. The first group is based directly on the
Damas/Milner rules (Figure 9). There are two small A.7 Principal typings
differences: translations have been added to each rule Given A and e, we call σ a principal type scheme for
in a straightforward way, and there are two TAUT e under A iff
rules instead of one (one rule for (::) bindings and
one for (::i ) bindings). • A ⊢ e :: σ \ e; and

15
• for every σ ′ , if A ⊢ e :: σ ′ \ e′ then σ ≥A σ ′ [DM82] L. Damas and R. Milner, Principal type
schemes for functional programs. In Pro-
A key result in the Hindley/Milner system is that ceedings of the 9’th Annual Symposium
every expression e that has a well-typing has a prin- on Principles of Programming Languages,
cipal type scheme. Albuquerque, N.M., January 1982.
We conjecture that for every valid set of assump-
tions A and every expression e containing no over or [FGJM85] K. Futasagi, J.A. Goguen, J.-P. Jouan-
inst expressions, if e has a well-typing under A then naud, and J. Meseguer, Principles of
e has a principal type scheme under A. OBJ2. In Proceedings of the 12’th An-
For example, let A0 be the set of assumptions in nual Symposium on Principles of Pro-
Figure 8. Then the typing gramming Languages, January 1985.

A0 ⊢ eq :: ∀α.Eq α \ eq (Eq alpha) [GR83] A. Goldberg and D. Robson, Smalltalk-


80: The Language and Its Implementa-
is principal. Examples of non-principal typings are tion. Addison-Wesley, 1983.
[Hin69] R. Hindley, The principal type scheme
A0 ⊢ eq :: Eq Int \ eq (Eq Int )
of an object in combinatory logic. Trans.
A0 ⊢ eq :: Eq Char \ eq (Eq Char )
Am. Math. Soc. 146, pp. 29–60, Decem-
ber 1969.
Each of these is an instance of the principal typing
under assumptions A0 . [HMM86] R. Harper, D. MacQueen, and R. Milner,
The existence of principal types is problematic for Standard ML. Report ECS-LFCS-86-2,
expressions that contain over and inst expressions. Edinburgh University, Computer Science
For example, let A1 and e1 be the assumption set Dept., 1986.
and expression in Figure 12. Then it is possible to
derive the typings [HMT88] R. Harper, R. Milner, and M. Tofte, The
definition of Standard ML, version 2. Re-
A1 ⊢ e1 :: Eq Int \ eqInt port ECS-LFCS-88-62, Edinburgh Uni-
A1 ⊢ e1 :: Eq Char \ eqChar versity, Computer Science Dept., 1988.

But there is no principal type! One possible resolu- [Kae88] S. Kaes, Parametric polymorphism. In
tion of this is to require that over and inst declara- Proceedings of the 2’nd European Sym-
tions have global scope. It remains an open question posium on Programming, Nancy, France,
whether there is some less drastic restriction that March 1988. LNCS 300, Springer-Verlag,
still ensures the existence of principal types. 1988.
[Mil78] R. Milner, A theory of type polymor-
phism in programming. J. Comput. Syst.
References Sci. 17, pp. 348–375, 1978.
[BEJ88] D. Bjørner, A. Ershov, and N.D. Jones, [Mil84] R. Milner, A proposal for Standard ML.
editors, Partial Evaluation and Mixed In Proceedings of the Symposium on Lisp
Computation, North-Holland, 1988 (to and Functional Programming, Austin,
appear). Texas, August 1984.
[CW85] L. Cardelli and P. Wegner, On under- [Mil87] R. Milner, Changes to the Standard ML
standing types, data abstraction, and core language. Report ECS-LFCS-87-33,
polymorphism. Computing Surveys 17, 4, Edinburgh University, Computer Science
December 1985. Dept., 1987.
[Car88] L. Cardelli, Structural subtyping and the [MP85] J. C. Mitchell and G. D. Plotkin, Ab-
notion of power type. In Proceedings of stract types have existential type. In Pro-
the 15’th Annual Symposium on Prin- ceedings of the 12’th Annual Symposium
ciples of Programming Languages, San on Principles of Programming Languages,
Diego, California, January 1988. January 1985.

16
[Rey85] J. C. Reynolds, Three approaches to
type structure. In Mathematical Foun-
dations of Software Development, LNCS
185, Springer-Verlag, 1985.
[Str67] C. Strachey, Fundamental concepts in
programming languages. Lecture notes
for International Summer School in Com-
puter Programming, Copenhagen, Au-
gust 1967.
[Tur85] D. A. Turner, Miranda: A non-strict
functional language with polymorphic
types. In Proceedings of the 2’nd Inter-
national Conference on Functional Pro-
gramming Languages and Computer Ar-
chitecture, Nancy, France, September
1985. LNCS 201, Springer-Verlag, 1985.
[Wan87] M. Wand, Complete type inference for
simple objects. In Proceedings of the Sym-
posium on Logic in Computer Science,
Ithaca, NY, June 1987. IEEE Computer
Society Press, 1987.

17

You might also like