Type Classes
Type Classes
Abstract Lämmel and Ostermann (2006) show that type classes are
Type classes were originally developed in Haskell as a dis- useful to solve several fundamental challenges in software
ciplined alternative to ad-hoc polymorphism. Type classes engineering and programming languages. In particular type
have been shown to provide a type-safe solution to impor- classes support retroactive extension: the ability to extend
tant challenges in software engineering and programming existing software modules with new functionality without
languages such as, for example, retroactive extension of needing to touch or re-compile the original source. Type
programs. They are also recognized as a good mechanism classes are also recognized (Bernardy et al. 2008; Garcia
for concept-based generic programming and, more recently, et al. 2007; Siek and Lumsdaine 2008) as a good mechanism
have evolved into a mechanism for type-level computation. for concept-based C++ style generic programming (Musser
This paper presents a lightweight approach to type classes and Stepanov 1988), and have more recently evolved into
in object-oriented (OO) languages with generics using the a mechanism for type-level computation (Chakravarty et al.
C ONCEPT pattern and implicits (a type-directed implicit pa- 2005b; Jones 2000; Schrijvers et al. 2008).
rameter passing mechanism). This paper also shows how Existing proposals for type-class-like mechanisms in OO
Scala’s type system conspires with implicits to enable, and languages are rather heavyweight. The JavaGI proposal is
even surpass, many common extensions of the Haskell type to extend Java with generalized interfaces and generalized
class system, making Scala ideally suited for generic pro- interface implementations. Similarly, the C++0X concepts
gramming in the large. proposal is to extend C++ with concepts and model (or
concept-map) declarations to express concept-interfaces and
Categories and Subject Descriptors D.3.2 [Programming their implementations. In some sense the additional con-
Languages]: Language Classifications—Functional Lan- structs in JavaGI and C++0X overlap with conventional OO
guages, Object-Oriented Languages interfaces and classes, which play similar roles for defining
the interfaces of objects and their implementations.
General Terms Languages
Type classes comprise various language constructs that
Keywords Type classes, C++ concepts, Abstract datatypes, can be understood in isolation. The first role of type classes
Scala is to define concepts: a set of requirements for the type pa-
rameters used by generic algorithms. For example, a sorting
1. Introduction algorithm on lists can be expressed generically, for any el-
ements of type T, provided that we know how to compare
Type classes were introduced in Haskell (Peyton Jones 2003)
values of type T. One way to achieve this in an OO language
as a disciplined way of defining ad-hoc polymorphic abstrac-
with generics is to define the sorting function as follows:
tions (Wadler and Blott 1989). There are several language
mechanisms that are inspired by type classes: Isabelle’s
def sort [T ] (xs : List [T ]) (ordT : Ord [T ]) : List [T ]
type classes (Haftmann and Wenzel 2006), Coq’s type
classes (Sozeau and Oury 2008), C++0X concepts (Gre- In this definition, the role of ordT is to provide the compar-
gor et al. 2006), BitC’s type classes (Shapiro et al. 2008), or ison function for elements of type T. The Ord [T ] interface,
JavaGI generalized interfaces (Wehr 2009). expressed as a trait in Scala,
Permission to make digital or hard copies of all or part of this work for personal or trait Ord [T ] {
classroom use is granted without fee provided that copies are not made or distributed def compare (a : T, b : T) : Boolean
for profit or commercial advantage and that copies bear this notice and the full citation
on the first page. To copy otherwise, to republish, to post on servers or to redistribute }
to lists, requires prior specific permission and/or a fee.
OOPSLA/SPLASH’10, October 17–21, 2010, Reno/Tahoe, Nevada, USA. defines the ordering concept. Concepts are implemented for
Copyright c 2010 ACM 978-1-4503-0203-6/10/10. . . $10.00
Reprinted from OOPSLA/SPLASH’10,, [Unknown Proceedings], October 17–21, particular types by a model, which corresponds to a type
2010, Reno/Tahoe, Nevada, USA., pp. 1–20. class instance in Haskell, or an object in Scala.
1
object intOrd extends Ord [Int ] { We should note that implicits have been part of Scala for
def compare (a : Int, b : Int) : Boolean = a 6 b a while now (Moors et al. 2008; Odersky et al. 2006) and, in
} the Scala community, the encoding of type classes using im-
plicits is folklore. However, as so often with folklore, it was
However, this simple OO approach has one important never written down coherently, while the more advanced fea-
limitation in practice: constraints such as ordT have to be tures have not been documented at all: later sections of this
explicitly passed to generic algorithms, like any other argu- paper describe Scala’s answer to overlapping instances (Pey-
ments. While for the definition of sort above this may not ton Jones et al. 1997), associated types (Chakravarty et al.
look too bad, many generic algorithms require multiple con- 2005b), as well as how Scala’s approach to type classes
straints on their type parameters, and passing all of these has surpasses Haskell type classes in some ways. These ad-
explicitly is cumbersome. vanced features are used to introduce the idea of specify-
The second role of type classes is to propagate constraints ing relations on types using implicits, which is illustrated
like ordT automatically, making generic algorithms conve- through several examples. Finally, we show that Scala has
nient and practical to use. Scala took inspiration from type excellent support for generic programming.
classes and introduced implicits: a mechanism for implicitly
passing arguments based on their types. Thus, in Scala, the Running the examples Most examples compile as-is us-
ordering constraint can be implicitly passed by adding an ing Scala 2.8.0. Some of the more advanced ones rely on ex-
implicit qualifier before the argument: perimental support for dependent method types, which must
be enabled using the −Xexperimental switch. Unfortunately,
def sort [T ] (xs : List [T ]) (implicit ordT : Ord [T ]) : List [T ] some examples are affected by bugs related to the interac-
tion between dependent method types and implicits. These
Likewise potential candidate models can be considered by
are fixed in a development branch1 , which will be merged
the compiler by being qualified with an implicit keyword:
into trunk shortly, and thus appear in nightly builds leading
implicit object intOrd extends Ord [Int ] . . . up to the 2.8.1 release.
2
• Consumer methods like show are the closest to typical classes (Peyton Jones et al. 1997), which lifts the restriction
OO methods. They take one argument of type a, and of a single type parameter:
using that argument they produce some result.
class Coerce a b where
• Binary methods like 6 can take two arguments of type a,
coerce :: a → b
and produce some result. This appears to show some con-
instance Coerce Char Int where
trast with OO programming, since it is well-known that
binary (and n-ary methods in general) are hard to deal coerce = ord
with (Bruce et al. 1995). However, we should note that instance Coerce Float Int where
type class binary method arguments are only statically coerce = floor
dispatched and not dynamically dispatched.
The class Coerce has two type parameters a and b and
• Factory methods such as read return a value of type a
defines a method coerce, which converts a value of type a
instead of consuming values of that type. In OOP factory into a value of type b. For example, by defining instances
methods can be dealt with in different ways (for example, of this class, we can define coercions from characters to
by using static methods). integers and from floating point numbers to integers.
Type class declarations express generic programming Overlapping instances Another common extension of
concepts (Bernardy et al. 2008), and the models (or im- type classes allows instances to overlap (Peyton Jones et al.
plementations) of these concepts are given by type classes 1997), as long as there is a most specific one. For example:
instances. For example
instance Ord a ⇒ Ord [a] where . . .
instance (Ord a, Ord b) ⇒ Ord (a, b) where
instance Ord [Int ] where . . .
(xa, xb) 6 (ya, yb) = xa < ya ∨ (xa ≡ ya ∧ xb 6 yb)
Despite two possible matches for [Int ], the compiler is able
declares a model of the ordering concept for pairs. In this
to make an unambiguous decision to which of these in-
case, the ordering model itself is parametrized by an order-
stances to pick by selecting the most specific one. In this
ing model for each of the elements of the pair. With the or-
case, the second instance would be selected.
dering constraints we can define a generic sorting function:
sort :: Ord a ⇒ [a] → [a] 3. Implicits
This means sort takes a list of elements of an arbitrary type This section introduces the Scala implementation of implic-
a and returns a list of the same type, as long as the type of its and shows how implicits provide the missing link for type
the elements is in the Ord type class, hence the Ord a ⇒ class programming to be convenient in OO languages with
context. A call to sort will only type check if a suitable type generics.
class instance can be found. Other than that, the caller does
3.1 Implicits in Scala
not need to worry about the type class context, as shown in
the following interaction with a Haskell interpreter: Scala automates type-driven selection of values with the
implicit keyword. A method call may omit the final argu-
Prelude > sort [(3, 5), (2, 4), (3, 4)]
ment list if the method definition annotated that list with the
[(2, 4), (3, 4), (3, 5)] implicit keyword, and if, for each argument in that list, there
One instance per type A characteristic of (Haskell) type is exactly one value of the right type in the implicit scope,
classes is that only one instance is allowed for a given type. which roughly means that it must be accessible without a
For example, the alternative ordering model for pairs prefix. We will describe this in more detail later.
To illustrate this, the following example introduces an
instance (Ord a, Ord b) ⇒ Ord (a, b) where
implicit value out, and a method that takes an implicit ar-
(xa, xb) 6 (ya, yb) = xa 6 ya ∧ xb 6 yb
gument o : PrintStream. The first invocation omits this argu-
in the same program as the previous instance is forbidden ment, and the compiler will infer out. Of course, the pro-
because the compiler automatically picks the right type class grammer is free to provide an explicit argument, as illus-
instance based on the type parameter of the type class. Since trated in the last line.
in this case there are two type class instances for the same import java.io.PrintStream
type, there is no sensible way for the compiler to choose one implicit val out = System.out
of these two instances.
def log (msg : String) (implicit o : PrintStream)
2.2 Common extensions = o.println (msg)
Multiple-parameter type classes A simple extension to log ("Does not compute!")]
Wadler and Blott’s proposal are multiple parameter type- log ("Does not compute!!") (System.err)
3
Note that the arguments in an implicit argument list are part trait Monoid [A] {
of the implicit scope, so that implicit arguments are propa- def binary op (x : A, y : A) : A
gated naturally. In the following example, logTm’s implicit def identity :A
argument o is propagated to the call to log. }
def logTm (msg : String) (implicit o : PrintStream) : Unit def acc [A] (l : List [A]) (implicit m : Monoid [A]) : A =
= log ("[" + new java.util.Date () + "]" + msg) l.foldLeft (m.identity) ((x, y) ⇒ m.binary op (x, y))
object A {
The implicit argument list must be the last argument list and
it may either be omitted or supplied in its entirety. However, implicit object sumMonoid extends Monoid [Int ] {
there is a simple idiom to encode a wildcard for an implicit def binary op (x : Int, y : Int) = x + y
argument. To illustrate this with our running example, sup- def identity =0
pose we want to generalize logTm so that we can specify an }
arbitrary prefix, and that we want that argument to be picked def sum (l : List [Int ]) : Int = acc (l)
up from the implicit scope as well. }
def logPrefix (msg : String) object B {
(implicit o : PrintStream, prefix : String) : Unit implicit object prodMonoid extends Monoid [Int ] {
= log ("[" + prefix + "]" + msg) def binary op (x : Int, y : Int) = x ∗ y
Now, with the following definition of the polymorphic def identity =1
method ?, }
def ?[T ] (implicit w : T) : T = w def product (l : List [Int ]) : Int = acc (l)
}
which “looks up” an implicit value of type T in the implicit val test : (Int, Int, Int) = {
scope, we can write logPrefix ("a") (?, "pre"), omitting the import A.
value for the output stream, while providing an explicit value
import B.
for the prefix. Type inference and implicit search will turn
the call ? into ?[PrintStream] (out), assuming out is in the val l = List (1, 2, 3, 4, 5)
implicit scope as before. (sum (l), product (l), acc (l) (prodMonoid))
Implicit scope When looking for an implicit value of }
type T, the compiler will consider implicit value definitions
(definitions introduced by implicit val, implicit object, or Figure 1. Locally scoped implicits in Scala.
implicit def), as well as implicit arguments that have type
T and that are in scope locally (accessible without prefix)
Furthermore, both implicits can be brought into scope us-
where the implicit value is required. Additionally, it will
ing import and the user can choose which implicit to use
consider implicit values of type T that are defined in the
by explicitly parameterizing a declaration that requires a
types that are part of the type T, as well as in the companion
monoid. Thus, the result of executing test is (15, 120, 120).
objects of the base classes of these parts. The set of parts of
Note that, if instead of acc (l) (prodMonoid) we had used
a type T is determined as follows:
acc (l) in the definition of test, an ambiguity error would be
• for a compound type T1 with . . . with Tn , the union of reported, because two different implicit values of the same
the parts of Ti , and T, type (prodMonoid and sumMonoid) would be in scope.
• for a parameterized type S [T1 , . . . , Tn ], the union of the
Implicit search and overloading To determine an implicit
parts of S and the parts of Ti , value, the compiler searches the implicit scope for the value
• for a singleton type p.type, the parts of the type of p, with the required type. If no implicit can be found for an
• for a type projection S # U, the parts of S as well as S # U implicit argument with a default value, the default value is
itself, used. If more than one implicit value has the right type,
there must be a single “most specific” one according to
• in all other cases, just T itself.
the following ordering, which is defined in more detail by
Figure 1 illustrates the local scoping of implicits. Two (Odersky 2010, 6.26.3).
models for Monoid [Int ] exist, but the scope each of them is An alternative A is more specific than an alternative B if
limited to the enclosing declaration. Thus the definition of the relative weight of A over B is greater than the relative
sum in object A will use the sumMonoid implicit. Similarly, weight of B over A. The relative weight is a score between 0
in the object B, product will use the prodMonoid implicit. and 2, where A gets a point over B for being as specific as B,
4
and another if it is defined in a class (or in its companion ob-
trait Ord [T ] {
ject) which is derived from the class that defines B, or whose
companion object defines B. Roughly, a method is as spe- def compare (x : T, y : T) : Boolean
cific as a member that is applicable to the same arguments, }
a polymorphic method is compared to another member after class Apple (x : Int) {}
stripping its type parameters, and a non-method member is object ordApple extends Ord [Apple] {
as specific as a method that takes arguments or type param-
def compare (a1 : Apple, a2 : Apple) = a1 .x 6 a2 .x
eters.
}
Finally, termination of implicit search is ensured by keep-
ing track of an approximation of the types for which an im- def pick [T ] (a1 : T, a2 : T) (ordA : Ord [T ]) =
plicit value has been searched already (Odersky 2010, 7.2). if (ordA.compare (a1 , a2 )) a2 else a1
val a1 = new Apple (3)
3.2 Implicits as the missing link
val a2 = new Apple (5)
Implicits provide the type-driven selection mechanism that
val a3 = pick (a1 , a2 ) (ordApple)
was missing for type class programming to be convenient in
OO. For example, the Ord type class and the pair instance
that was presented in Section 2 would correspond to: Figure 2. Apples to Apples with the C ONCEPT pattern.
trait Ord [T ] {
def compare (x : T, y : T) : Boolean Scala, implicit values that have a function type act as im-
} plicit conversions. For a method call such as x.compare (y)
to be well-typed, the type of x must either define a suitable
implicit def OrdPair [A, B] compare method, or there must be an implicit conversion c
(implicit ordA:Ord [A], ordB:Ord [B]) in scope so that (c (x)).compare (y) is well-typed without
= new Ord [(A, B)] { further use of implicit conversions. Thus, it suffices to de-
fine an implicit method (the compiler converts methods to
def compare (xs : (A, B), ys : (A, B)) = . . .
functions when needed) mkOrd that will convert a value of
}
a type that is in the Ord type class into an object that has the
Note that the syntactic overhead compared to Haskell (high- expected interface:
lighted in gray) includes useful information: type class in- implicit def mkOrd [T : Ord ] (x : T) : Ordered [T ]
stances are named, so that the programmer may supply them = new Ordered [T ] {
explicitly to resolve ambiguities manually.
def compare (o : T) = ?[Ord [T ]].compare (x, o)
The cmp function is rendered as the following method:
}
def cmp [a] (x : a, y : a) (implicit ord : Ord [a]) : Boolean
= ord.compare (x, y) Leaving off the target of the comparison in the compare
method, which has been passed to the implicit conversion
This common type of implicit argument can be abbreviated mkOrd, Ordered is defined as:
using context bounds (highlighted in gray): trait Ordered [T ] {
def cmp [a:Ord ] (x : a, y : a) : Boolean def compare (o : T) : Boolean
= ?[Ord [a]].compare (x, y) }
5
with generics. Concepts describe a set of requirements for object ordApple2 extends Ord [Apple] {
the type parameters used by generic algorithms. Figure 2 def compare (a1 : Apple, a2 : Apple) = a1 .x > a2 .x
shows a small variation of the apples-to-apples motivational }
example for concepts (Garcia et al. 2007). This example
serves the purpose of illustrating the different actors in the
C ONCEPT pattern. The trait Ord [T ] is an example of a 3. Binary (or n-ary) methods: Conceptual methods can have
concept interface. The type argument T of a concept inter- multiple arguments of the manipulated type. Thus a sim-
face is the modeled type; an Apple is a concrete modeled ple form of type-safe statically dispatched n-ary methods
type. Actual objects implementing concept interfaces such is possible.
as ordApple are called models. Finally, whenever ambigu- 4. Factory methods: Conceptual methods do not need an
ity arises, we will refer to methods in a concept interface as actual instance of the modeled type. Thus they can be
conceptual methods to distinguish them from conventional factory methods.
methods defined in the modeled type.
The C ONCEPT pattern can model n-ary, factory and con- Limitations and Alternatives The main limitation of the
sumer methods just like type classes. Concept interfaces for C ONCEPT pattern is that all arguments of conceptual meth-
the type classes Show and Read presented in Section 2.1 are: ods are statically dispatched. Thus, conceptual methods are
less expressive than conventional OO methods, which allow
trait Show [T ] { the self-argument to be dynamically dispatched, or multi-
def show (x : T) : String methods (Chambers and Leavens 1995), in which all argu-
} ments are dynamically dispatched.
Bounded polymorphism offers an alternative to type-
trait Read [T ] {
class-style concepts. With bounded polymorphism the apples-
def read (x : String) : T
to-apples example could be modeled as follows:
}
trait Ord [T ] {
The printf example in Section 2.1 presents an example def compare (x : T) : Boolean
of a concept-interface with a factory method. Most of the }
examples in this paper involve consumer methods. class Apple (x : Int) extends Ord [Apple] . . .
Multi-type Concepts Using standard generics it is possible
The main advantage of this approach is that compare is
to model multi-type concepts. That is, concepts that involve
a real, dynamically dispatched, method of Apple, and all the
several different modeled types. For example, the Coerce
private information about Apple objects is available for the
type class in Section 2.2 can be expressed as a concept
method definition. However, with this alternative, modeled
interface as:
types such as Apple have to state upfront which concept in-
trait Coerce [A, B] { terfaces they support. This precludes retroactive modeling
def coerce (x : A) : B and makes it harder to support multiple implementations of
} a method for the same object. Multi-type concepts are pos-
sible, but they can be quite cumbersome to express and they
The zipWithN example (in Section 6.4) and generalized can lead to a combinatorial explosion on the number of con-
constraints (in Section 6.6) provide applications of multi- cept interfaces (Järvi et al. 2003). Factory methods can be
type concepts. supported with this approach through a static method, al-
though this dictates a single implementation. Binary meth-
Benefits of the C ONCEPT pattern The C ONCEPT pattern ods such as compare are also possible, although they are
offers the following advantages: asymmetric in the sense that the first argument is dynami-
cally dispatched, whereas the second argument is statically
1. Retroactive modeling: The C ONCEPT pattern allows dispatched.
mimicking the addition of a method to a class with-
Language Support In languages such as Java or C# con-
out having to modify the original class. For example,
cepts need to be explicitly passed as in Figure 2. In Scala
in Figure 2, the declaration of Apple did not require any
it is possible to pass concepts implicitly as shown in Sec-
knowledge about the compare functionality upfront. The
tion 3.2. Additionally, in languages like Java or C#, there is
ordApple model adds support for such method externally.
some syntactic noise because the method cannot be invoked
2. Multiple method implementations: It is possible to have directly on the manipulated object:
multiple implementations of conceptual methods for the
same type. For example, an alternative ordering model a = new Apple (3);
for apples can be provided: a.compare (new Apple (5));
6
trait Eq [T ] {
is invalid. Instead, we must write:
def equal (a : T, b : T) : Boolean
a = new Apple (3); }
ordApple.compare (a, new Apple (5)); trait Ord [T ] extends Eq [T ] {
def compare (a : T, b : T) : Boolean
In Scala, as discussed in Section 3.2, it is possible to elim- def equal (a : T, b : T) : Boolean =
inate this overhead using implicits. All that is needed is 1) compare (a, b) ∧ compare (b, a)
mark the models (and any possible constraints) with implicit }
and 2) create a simple interface for the comparison function
that takes the ordering object implicitly. Thus provided that class IntOrd extends Ord [Int ] {
the apples-to-apples is modified as follows: def compare (a : Int, b : Int) = a 6 b
}
implicit object ordApple extends Ord [Apple] . . .
class ListOrd [T ] (ordD:Ord [T ]) extends Ord [List [T ]] {
def cmp [A] (x : A, y : A) (implicit ord : Ord [A]) =
ord.compare (x, y) def compare (l1 : List [T ], l2 : List [T ]) =
(l1, l2) match {
Then we can write: case (x :: xs, y :: ys) ⇒
if (ordD.equal (x, y)) compare (xs, ys)
a = new Apple (3);
else ordD.compare (x, y)
cmp (a, new Apple (5));
case ( , Nil) ⇒ false
case (Nil, ) ⇒ true
Modifying pick similarly,
}
def pick [T : Ord ] (a1 : T, a2 : T) = }
if (cmp (a1 , a2 )) a2 else a1
class ListOrd2 [T ] (ordD : Ord [T ]) {
allows rewriting the value a3 as: extends Ord [List [T ]] {
7
def cmp [T ] (x : T, y : T) (implicit ord : Ord [T ]) = trait Set [S] {
val empty : S
ord.compare (x, y)
def insert (x : S, y : Int) : S
implicit val IntOrd = new Ord [Int ] {. . .} def contains (x : S, y : Int) : Boolean
def union (x : S, y : S) : S
implicit def ListOrd [T ] (implicit ordD:Ord [T ]) =
}
new Ord [List [T ]] {. . .}
class ListSet extends Set [List [Int ]] {
def ListOrd2 [T ] (implicit ordD : Ord [T ]) =
val empty = List ()
new Ord [List [T ]] {. . .} def insert (x : List [Int ], y : Int) = y :: x
def contains (x : List [Int ], y : Int) = x.contains (y)
Figure 4. Variation of the Ordering solution using implicits. def union (x : List [Int ], y : List [Int ]) = x.union (y)
}
The three models illustrate the retroactive capabilities of class FunctionalSet extends Set [Int ⇒ Boolean] {
the C ONCEPT pattern: the models are added after Int and val empty = (x : Int) ⇒ false
List [T ] have been defined. The two models for lists illustrate def insert (f : Int ⇒ Boolean, y : Int) =
that multiple models can co-exist at the same time.
z ⇒ y.equals (z) ∨ f (z)
Comparison with Type Classes The essential difference def contains (f : Int ⇒ Boolean, y : Int) = f (y)
between the OO code in Figure 3 and the similar definitions def union (f : Int ⇒ Boolean, g : Int ⇒ Boolean) =
using type classes (which can be found in Figure 13) is y ⇒ f (y) ∨ g (y)
that models, and model arguments, need to be named. In }
Haskell, instances can be viewed as a kind of anonymous
objects, which only the compiler gets direct access to. This
partly explains why the definition of ListOrd2 is grayed out: Figure 5. An ADT signature and two implementations.
in Haskell two instances for the same modeled type are
forbidden. of the code in Figure 3. Only the differences are shown:
In the OO version, it is necessary to first create the models definitions are used instead of conventional OO classes to
explicitly. For example: define the models for Ord; and we use a definition cmp to
provide a nice interface to the compare method. The first two
def sort [T ] (xs : List [T ]) (ordT : Ord [T ]) : List [T ] = . . . models are implicit, but ListOrd2 cannot be made implicit
val l1 = List (7, 2, 6, 4, 5, 9) because it would clash with ListOrd. The client code for the
val l2 = List (2, 3) test functions is simplified, being comparable to the version
val test = new ListOrd (new IntOrd ()).compare (l1 , l2 ) with Haskell type classes. Furthermore, it is still possible to
define test2 .
val test2 = new ListOrd2 (new IntOrd ()).compare (l1 , l2 )
val test = cmp (l1 , l2 )
val test3 = sort (l1 ) (new ListOrd (new IntOrd ())) val test2 = cmp (l1 , l2 ) (ListOrd2)
val test3 = sort (l1 )
In the type class version, the equivalent code would be:
5.2 Abstract data types
sort :: Ord t ⇒ [t ] → [t ]
Cook (2009) shows that type classes can be used to imple-
l1 = [7, 2, 6, 4, 5, 9]
ment what is effectively the algebraic signature of an Ab-
l2 = [2, 3]
stract Data Type (ADT). Programs using these type classes
test = compare l1 l2 in a certain disciplined way have the same abstraction ben-
test3 = sort l1 efits as ADTs. Exploiting this observation, we now show a
simple and practical encoding of ADTs in an object-oriented
Clearly, in the OO version, the use of compare in test and
language with generics using the C ONCEPT pattern. ADT
test2 is less convenient than simply calling compare l1 l2 ,
signatures show an application of the pattern that is differ-
but it does offer the possibility of switching the implemen-
ent from how concepts are traditionally used. Additionally,
tation of the comparison operation in test2 . In test3 creating
it illustrates why passing a model explicitly is sometimes de-
the models explicitly is also somewhat verbose and inconve-
sirable.
nient.
Figure 5 models an ADT signature for sets of integers
Solution using implicits The convenience of type classes using the C ONCEPT pattern. The trait Set [S], the concept
can be recovered with implicits. Figure 4 shows a variation interface, defines the ADT signature for sets. The type S is
8
the modeled type. The method empty is an example of a test1 : ∀ S. Set [S] → Boolean
factory method: a new set is created without any previous
set instance. The methods insert and contains are examples which is the type-theoretic type corresponding to the Scala
of consumer methods: they act on existing instances of sets type of test1 .
to achieve their goal. Finally union provides an example of In other words, test1 has the existential type that pro-
a binary method: two set instances are needed to take their vides the information hiding of the equivalent program with
union. Two alternative models are shown: ListSet, using a ADTs. While it is certainly debatable whether or not the ex-
lists to model sets; and FunctionalSet, which uses a boolean istential should be placed in the actual ADT definition, con-
predicate instead. cept interfaces are a simple way to encode ADT-like pro-
The client programs using models of ADT signatures can grams in any OO language with generics. This provides an
be used in a very similar way to ADTs implemented using alternative answer to Cook’s dinner quiz on the relationship
ML modules (MacQueen 1984). For example: between objects an ADTs: in an OO language with generics,
ADT signatures can be viewed as concept interfaces, and im-
val setImpl1 = new ListSet () plementations of these signatures can be modeled as objects.
val setImpl2 = new FunctionalSet () Comparison with Type Classes There are no significant
def test1 [S] (s:Set [S]) : Boolean = differences between the OO version of the program and the
s.contains (s.insert (s.insert (s.empty, 5), 6), 6) Haskell version (which can be found in Figure 14), except
that the models need to be named. However, client code is
In this case two different implementations of sets, setImpl1 more interesting to compare. While in the OO version we
and setImpl2 , are created. The definition test1 takes a set write:
implementation and defines a program using that imple- def test1 [S] (s:Set [S]) : Boolean =
mentation. Importantly, the set implementation is polymor- s.contains (s.insert (s.insert (s.empty, 5), 6), 6)
phic on S, which means that any subclass of Set [S] will be
valid as an argument. In particular, both test1 (setImpl1 ) and the Haskell version of this code
test1 (setImpl2 ) are valid arguments.
A reasonable question to ask at this point is whether the test1 :: Set s ⇒ Bool
programs written with ADT signatures are actually related test1 = contains (insert (insert empty 5) 6)
to conventional programs using ADTs. We discuss this issue
does not work. The problem is that this program is ambigu-
next.
ous: since Haskell type classes work under the assumption
Where is the existential? In their seminal paper on ADTs, that every dictionary is inferred by the compiler, there is no
Mitchell and Plotkin (1988) show that “abstract types have straightforward way to tell test1 which specific instance of
existential type”. Formally an ADT can be viewed has two Set is to be used.
distinct parts: the ADT signature and the existential encap- Programs using ADT-like structures show how certain
sulating the type of the concrete representation: programs do not fit well with the implicit nature of type
classes. To be more precise, ADTs fit very well within the
SetADT = ∃ S. Set [S] “class” of programs that type classes capture, however ex-
The trait Set[S] defines only the signature, but the existential, plicitly passing “type class instances” (the models of ADT
which provides information hiding, is missing. This means signatures) is desirable. The C ONCEPT pattern solution is
that certain programs can exploit the concrete representa- better in this respect because the models can be explicitly
tion, breaking encapsulation. Still, as Cook observes, it is passed.
possible to enforce information hiding with some discipline 5.3 Statically-typed printf
and the help of the type system; if client programs do not
exploit the concrete representations of S then the same ben- Our final application of the C ONCEPT pattern is a statically-
efits of ADTs apply. To see why this is the case consider the typed version of the C printf function similar to the one pre-
equivalent type-theoretic version of the test1 program: sented by (Kennedy and Russo 2005). This example shows
that often it is possible to model structures resembling exten-
test1 : SetADT → Boolean sible (in the sense that new constructors can be added) gen-
test1 = λ s → eralized algebraic datatypes (GADTs) (Peyton Jones et al.
s.contains (s.insert (s.insert (s.empty, 5), 6), 6) 2006) using the C ONCEPT pattern.
Figure 6 shows the implementation of a simple version
Unfolding the SetADT type into its definition yields of the C-style printf function using the C ONCEPT pattern.
test1 : (∃ S. Set [S]) → Boolean The implementation exploits an insight by (Danvy 1998),
who realized that by changing the representation of the for-
and this type is isomorphic to mat string, it is possible to encode printf in a conventional
9
The advantage is that the format string can be chosen pre-
trait Format [A] {
cisely, as it it the case for the standard printf function.
def format (s : String) : A
} val fmt : Format [Int ⇒ Char ⇒ String] =
new S ("Int: ", new I (new S (" Char: ",
def printf [A] (format:Format [A]) : A =
new C (new S (".", new E)))))
format.format ("") val test = printf (fmt) (3) (’c’)
class I [A] (formatD:Format [A])
For example, we can construct format strings using the
extends Format [Int ⇒ A] {
instances of the S class. In Haskell, such flexibility is not eas-
def format (s : String) = i ⇒
ily available. Nevertheless, if such flexibility is not required,
formatD.format (s + i.toString) the dictionary is inferred in Haskell, making the similar pro-
} grams more compact.
class C [A] (formatD:Format [A]) test :: String
extends Format [Char ⇒ A] { test = printf (3 :: Int) ’c’
def format (s : String) = c ⇒
Finally, we should note that if we modify the OO version into
formatD.format (s + c.toString) a more idiomatic version using Scala’s implicits (as done for
} the ordering example in Section 5.1), then we can also infer
class E extends Format [String] { the same format strings as in the Haskell version.
def format (s : String) = s 5.4 Type class programs are OO programs
}
As we have seen so far, programs written with type classes
class S [A] (l : String, formatD : Format [A]) seem to have a close correspondence with OO programs.
extends Format [A] { Still, how can we more precisely pinpoint the relationship
between a Haskell type class program and an OO program?
def format (s : String) = formatD.format (s + l) One answer to this question, which we describe next,
} lies on the relationship between the dictionary transla-
tion (Wadler and Blott 1989) of Haskell type classes and
a simple form of the functional recursive records (Cook and
Figure 6. Printf as an instance of the C ONCEPT pattern
Palsberg 1994) encoding of OO programs.
The dictionary translation, which is used by most Haskell
Hindley-Milner type system. The basic idea of the OO ver- compilers, converts a program using type classes into a pro-
sion is to use a concept interface Format to represent the gram using no type classes. This translation is necessary be-
format string of printf . Noteworthy, the format conceptual cause type classes are a language mechanism of the source
method is a factory: it creates instances of the modeled types. language, but most Haskell compilers use core languages
Four different models are provided: one for integers, an- (usually a variant of System F), which does not have a native
other for characters, a termination string and string literals. notion of type classes.
One advantage of this solution is that it is easy to introduce Figure 7 shows how the program in Figure 13 looks
new format specifiers simply by creating a new model of the like after the dictionary translation. Like with the OO pro-
Format concept. grams, the parts that do not have direct correspondents in the
Comparison with Type Classes Like with the previous Haskell type class code are highlighted in gray. Essentially
two examples, the models and model arguments need to be what happens is that the type class Ord is translated into a
named in the OO version. An important difference is that, program using a record. Each instance becomes a value (or,
with type classes, we cannot implement a corresponding more precisely, a function that takes the dictionaries for the
instance for the S [A] model. The problem is that, to define class contexts, if any) that represents the corresponding dic-
S, a String argument is required but type class instances can tionary.
only take type class dictionaries in the instance constraints. In the translated code, many of the characteristics of the
Thus, the following is not allowed: OO version are present. One similarity is that the “instances”
and the arguments need to be named. For example, the corre-
instance (String, Format a) ⇒ Format a where . . . sponding value for the equality dictionary for lists is defined
as:
Another difference concerns the client code. In the OO ver-
sion, the formatting string needs to be explicitly constructed listOrd::Ord a → Ord [a]
and passed. This has both advantages and disadvantages. listOrd ordD = Ord . . .
10
data Ord a = Ord { else compare ordD x y
eq :: a → a → Bool, ...
compare :: a → a → Bool
The recursive records interpretation of OO programs also
}
helps explaining this (superficial) difference. In the OO
intOrd::Ord Int program there is an implicitly passed self-argument in
intOrd = Ord { compare (xs, ys) (this is sugar for this.compare (xs, ys)) and
eq = λ a b → compare intOrd a b ∧ this self-argument is a recursive call in the corresponding
recursive records interpretation. What is happening in the
compare intOrd b a,
dictionary translation is that recursive calls are directly used.
compare = λ x y → x 6 y In summary, the relationship between type classes and
} OO programs is this: every type class program translated us-
listOrd::Ord a → Ord [a] ing the dictionary translation corresponds to a OO program
listOrd ordD = Ord { encoded using a simple form of the recursive records func-
tional OO encoding.
eq = λ a b → compare (listOrd ordD) a b ∧
compare (listOrd ordD) b a, 6. Advanced Uses of Type Classes
compare = λ l1 l2 → case (l1 , l2 ) of This section briefly explains GHC Haskell’s associated types
(x : xs, y : ys) → and shows in detail how they can be encoded in Scala using
if (eq ordD x y) type members and dependent method types, which are also
then compare (listOrd ordD) xs ys first introduced. The encoding of associated types and other
advanced features of Scala (such as prioratized overlapping
else compare ordD x y implicits) are illustrated with three examples: type-safe ses-
( ,[]) → False sion types, an n-ary version of the zipWith function, and an
( , ) → True encoding of generalized constraints. We conclude this sec-
} tion with a description of the pattern that is common to all
of these examples.
Figure 7. Ordering after the dictionary translation.
6.1 Associated types in GHC Haskell
The Glasgow Haskell Compiler (GHC) (Peyton Jones et al.
Here, listOrd is the name of the dictionary constructor and 2009) is a modern Haskell implementation that provides
ordD is the name of the argument of the constructor. Another a type class mechanism that goes well beyond Wadler’s
similarity is that in the invocations of the compare methods: original proposal. Of particular interest is the use of type
the dictionary value for the method is also explicit. For classes for type-level computation using extensions such as
example, associated types (Chakravarty et al. 2005b).
Associated types are type declarations that are associated
compare ordD x y to a type class and that are made concrete in the class’s
instances, just like type class methods. For example, the
As it turns out, the dictionary translation version of the class Collects represents an interface for collections of type
Haskell program has so much in common with the OO ver- c with an associated type Elem c, which denotes the type of
sion because it corresponds to a simple form of the recursive the elements of c.
records functional encoding of the OO program.
class Collects c where
The only significant difference between the dictionary
type Elem c
translation version and the OO version, highlighted in gray
next, is that the Haskell version has some explicit recursive empty :: c
calls on listOrd ordD: insert :: c → Elem c → c
toList :: c → [Elem c]
listOrd :: Ord a → Ord [a]
Two possible instances are:
listOrd ordD = Ord {
... instance Collects BitSet where
compare = λ l1 l2 → case (l1 , l2 ) of type Elem BitSet = Char
(x : xs, y : ys) → ...
if (eq ordD x y) instance (Collects c, Hashable (Elem c)) ⇒
then compare (listOrd ordD) xs ys Collects (Array Int c) where
11
type Elem (Array Int c) = Elem c
def add server =
...
In {x : Int ⇒
The basic idea is that, for a BitSet collection, the associated In {y : Int ⇒ System.out.println ("Thinking")
element type should be characters. For arrays of values of Out (x + y,
some type c, the type of the elements should be the same as Stop ())}}
the type of the elements of c itself. def add client =
Associated types require type-level computation. In the
Out (3,
Collects example this manifests itself whenever a value of
Out (4, {System.out.println ("Waiting")
type Elem c is needed. For example, when using insert the
second argument has the type Elem c. For an array of bit sets In {z : Int ⇒ System.out.println (z)
Array Int BitSet the type Elem (Array Int BitSet) should be Stop ()}}))
evaluated to Char, in order for the type-checker to validate
suitable values for that call. This entails unfolding the asso- Figure 8. An example session.
ciated type definitions to conclude that the element type of
an array of bit sets is indeed a character.
This version of ? may be used to access the implicit value
6.2 Implicits and type members of type T. Its additional precision will be essential in the
Associated types fell out for free in Scala, with the introduc- examples below, which select type members on implicit ar-
tion of implicits, due to existing support for type members. guments. The following examples illustrate that the combi-
Before illustrating this, we briefly introduce type members, nation of implicit search and dependent method types is an
path-dependent types and dependent method types. interesting recipe for type-level combination.
A type member is a type that can be selected on an
object. Like its value-level counterpart, a type member may 6.3 Session types
be abstract, in which case it is similar to a type parameter, As our first example, we port a type class encoding of ses-
or it may be concrete, serving as a type alias. For type sion types (Honda 1993) by Kiselyov et al. (2009) to Scala.
safety, an abstract type member may only be selected on a Session types describe the relationship between the types of
path (Odersky et al. 2003), a stable (immutable) value. a pair of communicating processes. A process is composed
Technically, types may only be selected on types, but a of a sequence of smaller processes.
path p is readily turned into a singleton type p.type, which is For example, the server process in Figure 8 takes two in-
the type that is inhabited by exactly one value: the object ref- tegers arguments as inputs and returns the sum of these two
erenced by p. The type selection p.T, where T is a type mem- integers as output. The corresponding client performs the
ber defined in the type of p, is syntactic sugar for p.type # T. dual of the server process. The example uses the elementary
We say that a type that contains the type p.type depends on processes that are defined in Figure 9. The Stop process in-
the path p. The type p.T is a path-dependent type. dicates the end of communication, whereas In and Out pro-
Since a method argument is considered a stable value, a cesses are paired to specify the flow of information during
type may depend on it. A method type that depends on one the session. An In [a, b] process takes an input of type a, and
or more of the method’s arguments is called a dependent continue with a process of type b.2
method type. The simplest example of such a type arises in Since they are duals, add client and add server form
the following version of the identity method: a session. We capture the notion of duality in Figure 9.
The DualOf relation is defined in the comments using in-
def identity (x : AnyRef ) : x.type = x
ference rules. It is rendered in Scala as the Session trait,
Since values are not allowed in paths (for now), this which declares the relation on its type parameter S (which is
version of identity must be limited to references. Using this aliased as Self for convenience) and its abstract type mem-
identity, we can statically track that y and y2 are aliases: ber Dual, which corresponds to an associated type. Thus, an
instance of type Session [S] {type Dual = D} is evidence
val y = "foo" of S DualOf D; such a value witnesses this fact by describ-
val y2 : y.type = identity (y) ing how to compose an instance of S with an instance of D
(through the run method). StopSession, InDual and OutDual
For now, these types must be enabled explicitly by the describe what it “means” for each of the atomic process
−Xexperimental compiler flag in Scala 2.8. types to be in a session with their respective duals by con-
We can massage identity into a more precise version of structing the witness at the corresponding concrete types.
the implicit argument wildcard ? that we introduced earlier:
2 The + and − symbols denote, respectively, co-variance and contra-
def ?[T <: AnyRef ] (implicit w : T) : w.type = w variance of the type constructor in these type parameters (Emir et al. 2006).
12
sealed case class Stop case class Zero ()
sealed case class In [−A, +B] (recv : A ⇒ B) case class Succ [N ] (x : N)
sealed case class Out [+A, +B] (data : A, cont : B) trait ZipWith [N, S] {
trait Session [S] {type Self = S; type Dual type ZipWithType
type DualOf [D] = Session [Self ] {type Dual = D} def manyApp : N ⇒ Stream [S] ⇒ ZipWithType
def run (self : Self , dual : Dual) : Unit def zipWith : N ⇒ S ⇒ ZipWithType =
} n ⇒ f ⇒ manyApp (n) (repeat (f ))
/∗ }
StopDual def zWith [N, S] (n : N, s : S)
Stop DualOf Stop
∗/ (implicit zw : ZipWith [N, S]) : zw.ZipWithType =
implicit object StopDual extends Session [Stop] { zw.zipWith (n) (s)
type Dual = Stop implicit def ZeroZW [S] = new ZipWith [Zero, S] {
def run (self : Self , dual : Dual) : Unit = {} type ZipWithType = Stream [S]
} def manyApp = n ⇒ xs ⇒ xs
/∗ }
Cont DualOf ContD implicit def SuccZW [N, S, R]
InDual
In [Data, Cont ] DualOf Out [Data, ContD] (implicit zw : ZipWith [N, R]) =
∗/ new ZipWith [Succ [N ], S ⇒ R] {
implicit def InDual [D, C ] (implicit cont : Session [C ]) type ZipWithType = Stream [S] ⇒ zw.ZipWithType
= new Session [In [D, C ]] { def manyApp = n ⇒ xs ⇒ ss ⇒ n match {
type Dual = Out [D, cont.Dual ] case Succ (i) ⇒ zw.manyApp (i) (zapp (xs, ss))
def run (self : Self , dual : Dual) : Unit = }
cont.run (self .recv (dual.data ), dual.cont) }
}
/∗ Figure 10. N-ary zipWith.
Cont DualOf ContD
OutDual
Out [Data, Cont ] DualOf In [Data, ContD]
∗/ is the method’s (inferred) result type, which depends on its
cont argument. Using the DualOf type alias, the modelled
implicit def OutDual [D, C ] (implicit cont : Session [C ])
relation can be made more explicit:
= new Session [Out [D, C ]] {
type Dual = In [D, cont.Dual ] Session [In [D, C ]] # DualOf [Out [D, cont.Dual]]
def run (self : Self , dual : Dual) : Unit = The same type alias is used in the context bound on runSession’s
cont.run (self .cont, dual.recv (self .data )) D type parameter, which should be read as: “D must be cho-
} sen so that S is the dual of D”. In our mathematical notion,
this is rendered as S DualOf D. To run the session, the evi-
Figure 9. Session types. dence of S and D being in the DualOf relation is recovered
using the ? method.
The expression InDual has the type3 : def runSession [S, D : Session [S] # DualOf ]
(session : S, dual : D) =
[D, C ] (implicit cont : Session [C ]) Session [In [D, C ]] {
?[Session [S] # DualOf [D]].run (session, dual)
type Dual = Out [D, cont.Dual]}
def myRun = runSession (add server, add client)
This is a polymorphic dependent method type, where [D, C ]
denotes the universal quantification ∀ D, C., (implicit cont :
Session [C ]) describes the argument list, and 6.4 Arity-polymorphic ZipWith in Scala
Session [In [D, C ]] {type Dual = Out [D, cont.Dual]} Typically, functional programming languages like Haskell
contain implementations of zipWith functions for two list
3 Note that this type is not expressible directly in the surface syntax. arguments:
13
zipWith :: (a → b → c) → [a] → [b] → [c]
trait ZipWith [S] {
zipWith f (x : xs) (y : ys) = f x y : zipWith f xs ys
type ZipWithType
zipWith f = []
def manyApp : Stream [S] ⇒ ZipWithType
McBride (2002) generalized such definition into an n-ary def zipWith : S ⇒ ZipWithType =
zipWith function. f ⇒ manyApp (repeat (f ))
zipWithn :: (a1 → a2 → . . . → an ) → }
([a1 ] → [a2 ] → . . . → [an ]) class ZipWithDefault {
implicit def ZeroZW [S] = new ZipWith [S] {
In other words, zipWithn is a function that given a function type ZipWithType = Stream [S]
with n arguments and n lists, provides a corresponding ver-
sion of the n-ary zipWith function. Similar challenges arise in def manyApp = xs ⇒ xs
OO libraries that model databases as collections of N-tuples, }
where the operations on these tuples should work for any N. }
A more general variant of this problem is discussed by Kise- object ZipWith extends ZipWithDefault {
lyov et al. (2004). def apply [S] (s : S) (implicit zw : ZipWith [S])
This example essentially performs type-level computa- : zw.ZipWithType = zw.zipWith (s)
tion, since the return type [a1 ] → [a2 ] → . . . → [an ] is com-
puted from the argument type (a1 → a2 → . . . → an ). Fig- implicit def SuccZW [S, R]
ure 10 shows an implementation for the n-ary zipWith func- (implicit zw : ZipWith [R]) = new ZipWith [S ⇒ R] {
tion in Scala. The types Zero and Succ [N ] are Church en- type ZipWithType = Stream [S] ⇒ zw.ZipWithType
codings of the natural numbers at the type level. def manyApp = xs ⇒ ss ⇒
The ZipWith trait is a concept interface with two type zw.manyApp (zapp (xs, ss))
arguments: N represents a natural number; and S is the type }
of the function argument of zipWithn . The type member
}
ZipWithType determines the return type. The zWith method
is the interface for the n-ary zipWith function. The two
definitions ZeroZW and SuccZW provide two models that, Figure 11. N-ary zipWith using prioritised implicits.
respectively, correspond to the base case (for N = 0) and
the inductive case. The functions repeat and zapp, used in the implicits. Although type classes in Haskell support over-
zipWith and manyApp are defined as: lapping instances, this solution is not directly applicable to
Haskell.
def repeat [A] (x : A) : Stream [A] = cons (x, repeat (x)) Figure 11 shows the alternative solution for the n-ary
def zapp [A, B] (xs : Stream [A ⇒ B], ys : Stream [A]) = zipWith problem. Notably, the trait ZipWith and the meth-
(xs, ys) match { ods manyApp and zipWith are not parameterised by natural
case (cons (f , fs), cons (s, ss)) ⇒ numbers anymore.
cons (f (s), zapp (fs, ss)) When several implicit values are found for a certain type,
case ( , ) ⇒ Stream.empty disambiguation proceeds by the standard rules for static
} overloading. Finally, an additional tie-breaker rule is intro-
duced that gives priority to implicits that are defined in a
Some example client code is given next: subclass over those in a parent class. That is why SuccZW
will be preferred over ZeroZW in case of ambiguity.
def zipWith0 : Stream [Int ] = zWith (Zero (), 0) For example, both ZeroZW and SuccZW are possible
def map [A, B] (f : A ⇒ B) : Stream [A] ⇒ Stream [B] = matches when an implicit of type S ⇒ R is required. How-
zWith (Succ (Zero ()), f ) ever, the ambiguity is resolved because SuccZW is defined
def zipWith3 [A, B, C, D] (f : A ⇒ B ⇒ C ⇒ D) : in a subclass of ZipWithDefault, which defines ZeroZW.
Stream [A] ⇒ Stream [B] ⇒ Stream [C ] ⇒ Stream [D] = Using this solution, definitions for zipWith0 and map are:
zWith (Succ (Succ (Succ (Zero ()))), f ) def zipWith0 : Stream [Int ] = ZipWith (0)
def map [A, B] (f : A ⇒ B) : Stream [A] ⇒ Stream [B] =
6.5 ZipWith using prioritised overlapping implicits ZipWith (f )
Scala offers another solution for the n-ary zipWith prob-
lem, which avoids the explicit encoding of type-level nat- 6.6 Encoding generalized constraints
ural numbers. This solution relies on Scala’s support for dis- Generalized constraints (Emir et al. 2006) provide a way
ambiguating overlapping implicits by explicitly prioritizing to describe what it means for a type to be a subtype of
14
another type, and are used in practice in the Scala collection is applied in the Scala 2.8 collection library (Odersky and
libraries (Odersky and Moors 2009). For example, a classic Moors 2009) to specify how transformations on collections
use-case for generalized constraints is the flatten method of affect the types of the elements and their containers.
a collection with elements of type T: it is only applicable if Specifically, the CanBuildFrom [From, El, To] relation is
T is itself a collection. However, methods cannot constrain used to specify that the collection To can store elements of
the type parameters of the class in which they are defined. type El after transforming a collection of type From. This
We can represent this subtyping constraint indirectly as a relation can be thought of as a function from the types From
function from T to the collection type Traversable [U ]. This and El to the type of the derived collection To.
coercion can be seen as a witness to the subtype relation. The crucial feature that makes defining these relations on
Clearly, the caller of the method should not have to provide types practical is the connection between implicit search and
this witness explicitly. type inference. In fact, implicit search can be thought of as a
generalization of type inference to values. Thus, a relation on
sealed abstract class <:<[−S, +T ] extends (S ⇒ T)
types can be modeled by a type constructor of the same arity
implicit def conforms [A] : A <:< A = new (A <:< A) {
as the relation, where a tuple of types is considered in this
def apply (x : A) = x} relation if there is an implicit value of the type that results
trait Traversable [T ] { from applying the relation’s type constructor to the concrete
type Coll [X ] type arguments in the tuple.
def flatten [U ] (implicit w : T <:< Traversable [U ]) To summarize, the examples described in this section are
: Coll [U ] modeled using several type-level relations: the duality of
} two sessions; the relation between the argument and return
type of the n-ary zipWith function; and <:<, the generalized
To address this problem in Scala, we encode generalized constraint relation.
constraints using the type constructor <:<. The trick is to
use variance to extend the fact A <:< A (witnessed by the
implicit value conforms [A]) to the facts S <: T for any S
7. Discussion and Related Work
and T, where S <: A and A <: T. According to the variance This section discusses the results of this paper and presents
of the type constructor <:<, a value of type A <: A can be some related work. Also, an existing comparison between
used when a value of type S <: T is expected. When type different languages in terms of their support for generic
checking a call to flatten on a Traversable [List [Int ]], for programming in the large (Siek and Lumsdaine 2008) is
example, an implicit of type List [Int ] <:< Traversable [?U ] revised to include Scala and JavaGI. This comparison shows
is searched, where ?U denotes the type variable that is used that Scala is very suitable for generic programming in the
to infer the concrete type for U. Since conforms[List [Int ]] is large.
the only solution, ?U is inferred to be Int. Moreover, as <:<
is a (higher-order) subtype of ⇒, w is used as an implicit 7.1 Real-world applications
conversion in the body of flatten to convert expressions of Implicits are widely used by Scala programmers. The largest
type T to expressions of type Traversable [U ]. real-world application of some of the techniques presented
This example shows that functional dependencies (Jones in this paper is probably the newly designed Scala collec-
2000) are specified in Scala by the order of arguments and tions library that ships as part of Scala 2.8. Odersky and
their grouping into argument lists. Type inference proceeds Moors (2009) report their experiences in redesigning the
from left to right, with constraints being solved per argument Scala collections and argue that implicits, and their ability to
list, so that, once a type variable is solved, the arguments in a specify piece-wise type-level functions, play a crucial role in
later argument list have to abide. In implicit argument lists, their design.
the constraints that arise from the search for each implicit In retrospect, the results reported by Odersky and Moors
argument are solved immediately, so that implicit arguments are not too surprising. The C++ generic programming com-
must respect the instantiations of the type variables that munity has long learned to appreciate the value of associ-
result from these earlier arguments. Finally, as the implicit ated types to define such piece-wise functions for collec-
argument list must come last, implicit search cannot actively tion types. The developments presented in Section 6 show
guide type inference in the explicit argument lists, although how associated types can be more naturally defined us-
it can veto its results post factum. ing type members and dependent method types. The stan-
dard template library (STL) (Musser and Saini 1995) and
6.7 Type theories using implicits Boost (Boost) libraries are prime examples of generic pro-
To conclude this section, we briefly discuss the pattern gramming with C++ templates. In some sense, Scala’s 2.8
that underlies the previous examples: the idea of describ- collections can be viewed as the STL/Boost of Scala.
ing relations on types using implicits, which introduces a In the functional programming communities the term
lightweight form of type-level computation. This pattern “generic programming” is often used to mean datatype-
15
generic programming (DGP) (Gibbons 2003). DGP can be sociated datatypes (Chakravarty et al. 2005a), and type-
viewed as an advanced form of generic programming in families (Schrijvers et al. 2008) provide simple forms of
which the structure of types is used to derive generic algo- type-level computation. This work shows that by combin-
rithms. Oliveira and Gibbons (2008) show that Scala is par- ing type members and dependent method types it is pos-
ticularly well-suited for DGP and that it is, in some ways, sible to encode associated types, which are a fundamen-
better than Haskell. This is partly due to the flexibility of tal mechanism for supporting advanced forms of generic
implicits in comparison with type classes. programming (Garcia et al. 2007). Furthermore, prioritized
overlapping implicits allow the definition of type-level func-
7.2 Type classes, JavaGI and concepts
tions with a default, catch-all case, that can be overridden
Type classes Haskell type classes were originally designed by other cases in subclasses. This functionality provides a
as a solution for ad-hoc polymorphism (Hall et al. 1996; limited form of concept-based overloading (Siek and Lums-
Wadler and Blott 1989). Many languages have mechanisms daine 2008). In Haskell such overlapping definitions are for-
inspired by type classes (Gregor et al. 2006; Haftmann and bidden for associated types and type-families (Chakravarty
Wenzel 2006; Shapiro et al. 2008; Sozeau and Oury 2008; et al. 2005b; Schrijvers et al. 2008); and have limited appli-
Wehr 2009), and implicits are no exception. Implicits are the cability with functional dependencies. Type-families allow
minimal delta needed to enable type class programming in a natural definition of type-level functions, whereas type
an OOPL with support for parametric polymorphism. This members and dependent method types can only express such
extension can be seen as untangling type classes into the (ex- definitions indirectly. It would be interesting to explore a
isting) OO class system, and a mechanism for type-directed mechanism similar to type-families in the context of OO
implicit parameter passing. The most obvious downside of languages.
the untangling approach is that implicit values and implicit
JavaGI Inspired by Lämmel and Ostermann (2006), Wehr
arguments must play by the rules that also govern normal
(2009) proposes JavaGI: a variant of Java aimed at bring-
values and arguments: that is, they must be named. However,
ing the benefits of type classes into Java. JavaGI provides
these names are useful when disambiguation is needed, and,
an alternative viewpoint in the design space of type-class-
at least for implicit arguments, they can be eschewed using
like mechanisms in OO languages. JavaGI has generalized
context bounds. Finally, the OO philosophy of limiting type
interfaces and generalized interface implementations in ad-
inference to the inside of encapsulation boundaries entails
dition to conventional OO interfaces and classes. Scala,
that context bounds are not inferred, unlike in Haskell.
on the other hand, models type-class-like mechanisms us-
Nevertheless, this untangling has several benefits. Im-
ing the conventional OO class system. JavaGI supports a
plicit arguments may still be passed explicitly when nec-
form of dynamic multiple dispatching in a similar style to
essary, while Haskell requires duplication when the dictio-
multi-methods (Chambers and Leavens 1995; Clifton et al.
nary needs to be passed explicitly. For example a sort func-
2000). This allows JavaGI to express open classes (Clifton
tion (in which the ordering dictionary is passed implicitly),
et al. 2000) through retroactive implementations. Scala is
and a sortBy function (in which the ordering dictionary is
less expressive in this respect as it does not support dy-
passed explicitly) are needed in Haskell. Furthermore, since
namic multiple dispatching; only static multiple dispatching
a type class is encoded as a first-class type, the language’s
is supported. However, Scala supports fully modular type-
full range of polymorphism applies. In Haskell, it is not pos-
checking, which is not the case for JavaGI.
sible to directly abstract over a type class (Hughes 1999).
Several authors noted that Haskell type classes can be Concepts Concepts (Musser and Saini 1995) are an impor-
limiting for certain applications due to the impossibility tant notion for C++ style generic programming. They de-
of explicitly passing arguments and abstracting over type scribe a set of requirements for the type parameters used by
classes (Dijkstra and Swierstra 2005; Hughes 1999; Kahl a generic algorithm. Although concepts are widely used by
and Scheffczyk 2001; Lämmel and Jones 2005; Oliveira and the C++ community to document generic algorithms, current
Gibbons 2008; Oliveira and Sulzmann 2008). Thus, there versions of C++ have no representation for concepts within
have been a number of proposals aimed at lifting some of the C++ language. Nevertheless the situation is likely to
these restrictions (Dijkstra and Swierstra 2005; Kahl and change. Gregor et al. (2006) proposed linguistic support for
Scheffczyk 2001; Orchard and Schrijvers 2010). However, concepts in C++. Moreover, motivated by the lack of modu-
none of these proposals has been adopted in Haskell. There lar typechecking and separate compilation in C++, concepts
is also a proposal for an ML-module system that allows have been studied in the F G calculus (Siek and Lumsdaine
modules to be implicitly passed based on their types, thus 2005) and the G language (Siek and Lumsdaine 2008).
allowing many typical type class programs to be recovered Concepts are closely related to type classes and there is a
by suitably marking module parameters as implict (Dreyer fairly accepted idea that type classes can be viewed as an al-
et al. 2007). ternative language mechanism to express concepts (Bernardy
Type class extensions such as functional dependencies (Jones et al. 2008). Indeed, in several comparative studies, type
2000), associated types (Chakravarty et al. 2005b), as- classes score very well when it comes to language sup-
16
C++ SML OCaml Haskell Java C# Cecil C++0X G JavaGI Scala
Multi-type concepts - # 2 2 #
G 2
Multiple constraints - #
G #
G
Associated type access #
G G
# G
# #
G G
# 1
Retroactive modeling - #2
G #2
G 23
Type aliases # # # #
Separate compilation # #
G #
Implicit arg. deduction # G5
# G5
# #
G 3
Figure 12. Level of support for generic programming in several languages. Key: =‘good’, G #=‘sufficient’, #=‘poor’
support. The rating “-” in the C++ column indicates that while C++ does not explicitly support the feature, one can still
program as if the feature were supported. Notes: 1) supported via type members and dependent method types 2) supported via
the C ONCEPT pattern 3) supported via implicits 4) partially supported by prioritized overlapping implicits 5) decreased score
due to the use of the C ONCEPT pattern
port for generic programming (Garcia et al. 2007; Siek and the scores for JavaGI and Scala and adjusted a couple of
Lumsdaine 2008) (see also Figure 12). However, there are scores in Java and C#. The scores for JavaGI are derived
some differences in purpose between type classes and im- from the related work discussions by Wehr (2009); we did
plicits, and concepts in C++. In C++ performance is con- not do any experiments with JavaGI. These scores and ad-
sidered as a critical requirement, and the template mecha- justments are discussed next.
nism is designed so that, at compile-time, templated code
is specialized. Thus a potential mechanism for expressing Adjustments on Java and C# scores In the original com-
concepts in C++ should not jeopardize these performance parison, Siek and Lumsdaine (2008) give Java and C# bad
benefits (Dos Reis and Stroustrup 2006). In contrast, the scores at both multi-type concepts and retroactive model-
main motivation for type classes and implicits is abstraction ing. The main reason for those scores is that concepts are
and convenience, providing additional flexibility through modeled by a conventional subtyping hierarchy. For exam-
indirection, at the potential cost of run-time performance. ple, the Comparable concept and the corresponding Apple
Although Scala already supports some user-driven type spe- model, are implemented as follows:
cialization (Dragos and Odersky 2009), further work needs
to be done to investigate whether implicits could be adapted trait Comparable [T ] {
to a system supporting full compile-time specialization. def better (x : T) : Boolean
The C ONCEPT pattern is aimed at expressing concepts }
using a standard OO class system without the performance public class Apple extends Comparable [Apple] {. . .}
constraints of C++. In an OO language like Scala, the C ON -
CEPT pattern, combined with implicits and support for asso- As discussed in Section 4 this solution is problematic
ciated types through type members and dependent method for supporting retroactive modeling and multi-type concepts.
types, provides an effective platform for generic program- In contrast, if the C ONCEPT is used, models can be added
ming in the large. retroactively and multi-type concepts can be expressed con-
veniently. The drawback of the C ONCEPT solution in lan-
7.3 Generic programming in the large guages like Java or C#, as also discussed in Section 4, is that
In studies by Garcia et al. (2007); Siek and Lumsdaine there is some additional overhead to use conceptual meth-
(2008), support for generic programming in several differ- ods. This is reflected in the score for retroactive modeling,
ent languages is investigated, with particular emphasis on which is only sufficient, since support for this feature is not
how such languages can model concepts. Figure 12 shows as natural as it could be. Also, the score for implicit argu-
the level of support for generic programming in various lan- ment deduction is affected by the use of the C ONCEPT pat-
guages. For the most part the scores are inherited from the tern because the concept constraints have to be passed ex-
study presented by Siek and Lumsdaine (2008). We added plicitly. In Java and C#, the original solution using bounded
17
polymorphism is better in this respect because no such over- types make Scala an language ready for generic program-
head exists. ming in the large.
JavaGI Since JavaGI is a superset of Java, it inherits most
of its scores. In JavaGI retroactive modeling and multi-type Acknowledgments
concepts are naturally supported through generalized inter- This work benefited from several discussions that we had
faces. Wehr (2009) explicitly states that lexically scoped with William Cook. We are grateful to Tom Schrijvers,
models and concept-based overloading are not supported. Jonathan Shapiro and Marcin Zalewski for their useful com-
Furthermore, although type-checking is mostly modular, a ments. Bruno Oliveira was supported by the Engineering
final global check is necessary. Thus JavaGI only partially Research Center of Excellence Program of Korea Ministry
supports modular type-checking. of Education, Science and Technology (MEST) / Korea Sci-
Scala Using the C ONCEPT pattern we can model multi- ence and Engineering Foundation (KOSEF) grant number
type concepts, multiple constraints and support retroactive R11-2008-007-01002-0.
modeling. Furthermore, Scala’s support for implicits means
that the drawbacks of the Java and C# solutions in terms of
References
the additional overhead, do not apply to Scala. Thus, Scala J. P. Bernardy, P. Jansson, M. Zalewski, S. Schupp, and A. Priesnitz.
scores well in both the implicit argument deduction and the A comparison of C++ concepts and Haskell type classes. In
WGP ’08, pages 37–48, 2008.
retroactive modeling criteria. Section 6 shows that associ-
ated types are supported in Scala through type members Boost. The Boost C++ libraries. http://www.boost.org/, 2010.
and dependent method types, and type members can also K. Bruce, L. Cardelli, G. Castagna, G. T. Leavens, and B. Pierce.
be used as type aliases. As shown in Section 3, Scala sup- On binary methods. Theor. Pract. Object Syst., 1(3):221–242,
ports lexically scoped models. Furthermore type-checking 1995.
is fully modular. Prioritized overlapping implicits provide P. Canning, W. Cook, W. Hill, W. Olthoff, and J. C. Mitchell. F-
some support for concept-based overloading as illustrated by bounded polymorphism for object-oriented programming. In
the zipWithN example in Section 6.5. However, overlapping FPCA ’89, pages 273–280, 1989.
models have to be structured using a subtyping hierarchy, M. Chakravarty, G. Keller, S. Peyton Jones, and S. Marlow. Asso-
which may not always be desirable. Thus, the score for this ciated types with class. pages 1–13, 2005a.
feature is only sufficient. Finally, Scala has full support for M. Chakravarty, G. Keller, and Simon Peyton Jones. Associated
first-class functions and it also supports equality constraints. type synonyms. In ICFP ’05, pages 241–253, 2005b.
In summary Scala turns out to be a language with excel- C. Chambers and G. T. Leavens. Typechecking and modules for
lent support for generic programming features, managing to multimethods. ACM Transactions on Programming Languages
fare at the same level, or even slightly better, than G (which and Systems, 17(6):805–843, 1995.
was specially designed as a language for generic program- C. Clifton, G. T. Leavens, C. Chambers, and T. Millstein. Multi-
ming in the large) or Haskell (which has been recognized Java: modular open classes and symmetric multiple dispatch for
has having very good support for generic programming). Java. In OOPSLA ’00, pages 130–145, 2000.
W. R. Cook. On understanding data abstraction, revisited. SIG-
8. Conclusion PLAN Not., 44(10):557–572, 2009.
This paper shows that it is possible to have the benefits of W. R. Cook and J. Palsberg. A denotational semantics of inheri-
type classes in a standard OO language with generics, by tance and its correctness. Inf. Comput., 114(2):329–350, 1994.
using the C ONCEPT pattern to express type-class style pro- O. Danvy. Functional unparsing. J. Funct. Program., 8(6):621–
grams. However some convenience is lost, especially for tra- 625, 1998.
ditional applications aimed at using type classes for con- A. Dijkstra and S. D. Swierstra. Making implicit parameters ex-
strained polymorphism. plicit. Technical report, Institute of Information and Computing
Implicits are a modest extension that can be added to stat- Sciences, Utrecht University, 2005. URL http://www.cs.uu.
ically typed languages. Implicits bring back the convenience nl/research/techreps/UU-CS-2005-032.html.
of use of type classes, but they have wider applicability and G. Dos Reis and B. Stroustrup. Specifying C++ concepts. In POPL
are useful in several other domains. With the improved sup- ’06, pages 295–308, 2006.
port for type-inference that we are already seeing in main- I. Dragos and M. Odersky. Compiling generics through user-
stream languages like Java or C#, it is only natural to expect directed type specialization. In ICOOOLPS ’09, pages 42–47,
that implicits will eventually find their way into those lan- 2009.
guages. D. Dreyer, R. Harper, M. M. T. Chakravarty, and G. Keller. Modular
Type members and dependent method types add extra type classes. In POPL ’07, pages 63–70, 2007.
power to the language and a combination of the two mech- B. Emir, A. Kennedy, C. V. Russo, and D. Yu. Variance and
anisms allows associated types to be expressed. In combi- generalized constraints for C# generics. In ECOOP, pages 279–
nation with implicits, type members and dependent method 303, 2006.
18
R. Garcia, J. Jarvi, A. Lumsdaine, J. Siek, and J. Willcock. An M. Odersky. The Scala Language Specification, Version 2.8. EPFL,
extended comparative study of language support for generic 2010. URL http://www.scala-lang.org/docu/files/
programming. J. Funct. Program., 17(2):145–205, 2007. ScalaReference.pdf.
J. Gibbons. Patterns in datatype-generic programming. In The M. Odersky and A. Moors. Fighting bit rot with types (experience
Fun of Programming, Cornerstones in Computing, pages 41–60. report: Scala collections). In FSTTCS, pages 427–451, 2009.
Palgrave, 2003. M. Odersky, V. Cremet, C. Röckl, and M. Zenger. A nominal theory
D. Gregor, J. Järvi, J. Siek, B. Stroustrup, G. Dos Reis, and of objects with dependent types. In ECOOP03, pages 201–224.
A. Lumsdaine. Concepts: linguistic support for generic pro- Springer-Verlag, 2003.
gramming in C++. In OOPSLA ’06, pages 291–310, 2006. M. Odersky, P. Altherr, V. Cremet, I. Dragos, G. Dubochet, B. Emir,
F. Haftmann and M. Wenzel. Constructive type classes in Isabelle. S. McDirmid, S. Micheloud, N. Mihaylov, M. Schinz, L. Spoon,
In TYPES, pages 160–174, 2006. E. Stenman, and M. Zenger. An Overview of the Scala Program-
ming Language (2. edition). Technical report, EPFL, 2006.
C. V. Hall, K. Hammond, S. Peyton Jones, and P. Wadler. Type
classes in Haskell. ACM Trans. Program. Lang. Syst., 18(2): B. C. d. S. Oliveira and J. Gibbons. Scala for generic programmers.
109–138, 1996. In WGP ’08, pages 25–36, 2008.
K. Honda. Types for dynamic interaction. In CONCUR ’93, pages B. C. d. S. Oliveira and M. Sulzmann. Objects to unify type classes
509–523, 1993. and GADTs. URL http://www.comlab.ox.ac.uk/people/
Bruno.Oliveira/objects.pdf. April 2008.
J. Hughes. Restricted data types in Haskell. In Haskell Workshop,
1999. D. Orchard and T. Schrijvers. Haskell type constraints unleashed.
In FLOPS ’10. Springer-Verlag, 2010.
J. Järvi, A. Lumsdaine, J. Siek, and J. Willcock. An analysis of con-
strained polymorphism for generic programming. In MPOOL S. Peyton Jones, editor. Haskell 98 Language and Libraries –
’03, page 87–107, 2003. The Revised Report. Cambridge University Press, Cambridge,
England, 2003.
M. P. Jones. Type classes with functional dependencies. In ESOP
’00, pages 230–244, 2000. S. Peyton Jones, M. Jones, and E. Meijer. Type classes: exploring
the design space. In Haskell Workshop, 1997.
W. Kahl and J. Scheffczyk. Named instances for Haskell type
S. Peyton Jones, D. Vytiniotis, S. Weirich, and G. Washburn. Sim-
classes. In Haskell Workshop, 2001.
ple unification-based type inference for GADTs. In ICFP ’06,
A. Kennedy and C. V. Russo. Generalized algebraic data types and pages 50–61, 2006.
object-oriented programming. OOPSLA ’05, pages 21–40, 2005.
S. Peyton Jones, S. Marlow, et al. The Glasgow Haskell Compiler,
O. Kiselyov, R. Lämmel, and K. Schupke. Strongly typed hetero- 2009. URL http://www.haskell.org/ghc/.
geneous collections. In Haskell ’04, pages 96–107, 2004.
T. Schrijvers, S. Peyton Jones, M. Chakravarty, and M. Sulzmann.
O. Kiselyov, S. Peyton Jones, and C. Shan. Fun with type functions, Type checking with open type functions. In ICFP ’08, pages
2009. URL http://research.microsoft.com/en-us/um/ 51–62, 2008.
people/simonpj/papers/assoc-types/.
J. S. Shapiro, S. Sridhar, and M. S. Doerrie. BitC language speci-
R. Lämmel and S. P. Jones. Scrap your boilerplate with class: fication, 2008. URL http://www.coyotos.org/docs/bitc/
extensible generic functions. In ICFP ’05, pages 204–215, 2005. spec.html.
R. Lämmel and K. Ostermann. Software extension and integration J. G. Siek and A. Lumsdaine. Essential language support for
with type classes. In GPCE ’06, pages 161–170, 2006. generic programming. In PLDI ’05, pages 73–84, 2005.
D. B. MacQueen. Modules for Standard ML. In LISP and Func- J. G. Siek and A. Lumsdaine. A language for generic programming
tional Programming, pages 198–207, 1984. in the large. Science of Computer Programming, In Press, Cor-
C. McBride. Faking it: Simulating dependent types in Haskell. J. rected Proof, 2008. URL http://www.sciencedirect.
Funct. Program., 12(5):375–392, 2002. com/science/article/B6V17-4TJ6F7D-1/2/
7d624b842e8dd84e792995d3422aee21.
J. C. Mitchell and G. D. Plotkin. Abstract types have existential
type. ACM Trans. Program. Lang. Syst., 10(3):470–502, 1988. M. Sozeau and N. Oury. First-class type classes. In TPHOLs ’08,
pages 278–293, 2008.
A. Moors, F. Piessens, and M. Odersky. Generics of a higher kind.
In OOPSLA ’08, pages 423–438, 2008. P. Wadler and S. Blott. How to make ad-hoc polymorphism less ad
hoc. In POPL ’89, pages 60–76, 1989.
D. Musser and A. A. Stepanov. Generic programming. In Symbolic
S. Wehr. JavaGI: A Language with Generalized Interfaces. PhD
and algebraic computation: ISSAC 88, pages 13–25. Springer,
thesis, University of Freiburg, Department of Computer Science,
1988.
December 2009.
D. R. Musser and A. Saini. The STL Tutorial and Reference
Guide: C++ Programming with the Standard Template Library.
Addison Wesley Longman Publishing Co., Inc., Redwood City,
CA, USA, 1995.
M. Odersky, 2006. URL http://www.artima.com/weblogs/
viewpost.jsp?thread=179766.
19
class Ord a where
eq :: a → a → Bool class Format a where
compare :: a → a → Bool format :: String → a
instance Ord Int where printf :: Format a ⇒ a
eq a b = compare a b ∧ compare b a printf = format ""
compare x y = x 6 y instance Format a ⇒ Format (Int → a) where -- I
instance Ord a ⇒ Ord [a] where format s = λ i → format (s ++ show i)
eq a b = compare a b ∧ compare b a instance Format a ⇒ Format (Char → a) where -- C
compare l1 l2 = case (l1, l2) of format s = λ c → format (s ++ show c)
(x : xs, y : ys) → if (eq x y) then compare xs ys instance Format String where -- E
else compare x y format s = s
( , [ ]) → False
Figure 15. Printf using Type Classes (cf. Figure 6)
( , ) → True
Figure 13. Ordering concept in Haskell (cf. Figure 3)
20