Polymorphism is a programming language feature that allows one interface to be used for a general class of actions.[1] Scalaz makes extensive use of ad-hoc polymorphism to provide its set of goodies. In this post I’ll cover ad-hoc polymorphism in detail and show you how you can implement ad-hoc polymorphism in Scala. I’ll also talk about parametric, and subtype polymorphism.
Parametric Polymorphism
In parametric polymorphism, the behavior of the function does not depend upon the type of the arguments passed to it. More formally[2],
Parametric polymorphism refers to when the type of a value contains one or more (unconstrained) type variables, so that the value may adopt any type that results from substituting those variables with concrete types.
1 | scala> def head[A](xs: List[A]) = xs(0) |
Here, the argument xs
has an unconstrained type A
which can be anything and yet head
would work. Then we call head
with lists of concrete type Int
, and String
resulting in xs
being of type List[Int]
and List[String]
.
Subtype Polymorphism
Subtype polymorphism involves inheritance. More formally[3],
Subtype polymorphism is a form of type polymorphism in which a subtype is a data-type that is related to another data-type (the super-type) by some notion of substitutability.
As an example[4], consider a trait Plus
which lets its subtype add itself with another of the same type.
1 | scala> trait Plus[A] { |
The function plus[A <: Plus[A]]
will work only with those arguments that are subtype of Plus
. This is restrictive because for this to work, the trait needs to be mixed in at the time of defining whatever concrete type A
represents.
Ad-hoc Polymorphism
In ad-hoc polymorphism the behavior of the function depends on what the type of the argument is. More formally[5],
Ad-hoc polymorphism refers to when a value is able to adopt any one of several types because it, or a value it uses, has been given a separate definition for each of those types.
The simplest way to do ad-hoc polymorphism is by overloading functions. For example, having plus(Int, Int)
, plus(Currency, Currency)
, etc. The other ways are by using type classes, and coercion.
Type Classes
I’ll rewrite the Plus[A]
example using type classes:
1 | scala> case class Currency(amount: Float, code: String) |
Relating the above implementation to the formal definition, the implicit p
was able to adopt one of CurrencyPlus
or KilogramPlus
depending on what arguments were passed. Thus the behavior of plus
is polymorphic as its behavior comes from the definition of each of the types.
Coercion
The other way to implement ad-hoc polymorphism is coercion. Coercion refers to implicitly converting the argument into a type that the function expects. Here’s an example modeled around scala.runtime.RichInt:
1 | scala> class AwesomeInt(val self: Int) { |
The absolute
method is defined in AwesomeInt
but what we have is a plain old Int
. This Int
gets transformed into an AwesomeInt
automagically because we have an implicit conversion within scope. The Int
was coerced into an AwesomeInt
.
So we see how ad-hoc polymorphism allows us to extend classes to whose code we don’t have access. We defined absolute
to work with Int
which comes from the Scala core library.
Higher-Kinded Types (HKT)
In the post where we generalized sum
function, we generalized it to work with a List
. What if we want to generalize it even further so that it can work with not just list but anything? Here’s what we want to achieve:
1 | // we have this |
The difference between the two is that the first version, although polymorphic over types for which we have monoids defined, is still heavily tied to List
. The second version instead will work with type that is FoldLeft
. We’ll expand upon the sum
example[6]. We’ll need the monoids and we’ll need to add a FoldLeft
to make sum
more generic.
1 | scala> import scala.language.higherKinds |
So, by using HKT, we can generalize across types.
Pimp My Library
You might wonder what the whole point of using so much polymorphism is. The answer is that it lets you inject new functionality into existing libraries without having access to their source code. To quote Martin Odersky[7]:
There’s a fundamental difference between your own code and libraries of other people: You can change or extend your own code, but if you want to use some other libraries you have to take them as they are … Scala has implicit parameters and conversions. They can make existing libraries much more pleasant to deal with.
So, by using all the polymorphism techniques, we can implement the “pimp my library” pattern.[8]
The Pimp my Library Pattern suggests an approach for extending a library that nearly does everything that you need but just needs a little more. It assumes that you do not have source code for the library of interest.
Conclusion
The polymorphism capabilities provided by Scala allow Scalaz to provide its set of features. By using HKT we can write code that truly generalizes across multiple types. It might seem like a long-winded way to do something so simple but as codebase gets larger and larger, the features that Scalaz provides out-of-the-box really help in eliminating boilerplate code. We haven’t seen how to use Scalaz, yet. These posts serve as a foundation to understand how Scalaz does what it does.