A lot of what Scalaz does is made possible by using ad-hoc polymorphism, traits, and implicits. I’ll explain how this works by borrowing from Nick Partridge’s talk on Scalaz.
Motivating Example
Let’s begin with a simple sum
function that adds together all the elements in a List[Int]
.
1 | scala> def sum(xs: List[Int]): Int = xs.foldLeft(0)( _ + _ ) |
The sum
function above works only with List[Int]
. If we want to sum together a List[Double]
, List[Float]
, or a List[String]
, we’d need a new implementation of sum
. Our goal is to make sum
function general so it would work with any of these.
Step 1 - Monoid
The first step towards generalizing sum
is by using a monoid. A monoid is an algebraic structure with a single associative binary operation and an identity element.[1]. Since we are working with a List[Int]
, let’s create an IntMonoid
.
1 | scala> object IntMonoid { |
mempty
is the identity or the zero value, and mappend
is the binary operation which produces another Int
i.e. another value in the set. These names come from Haskell[2].
Step 2 - Generalizing the Monoid
Next, we’ll generalize the monoid by creating a Monoid[A]
so that IntMonoid
is just a monoid on Int
.
1 | scala> trait Monoid[A] { |
What we’ve done is create a general-purpose sum
function whose working depends upon which monoid is passed to it. Now we can very easily sum a List[String]
or a List[Double]
by adding a corresponding monoid.
Step 3 - Make the Monoid Implicit
Next, we’ll make the monoid an implicit parameter to our sum
function. We’ll also package our IntMonoid
into a Monoid
companion object and make it implicit. The reason for doing this is how Scala compiler resolves implicit values; it’ll look for implicit values in its scope. So, we bring IntMonoid
within scope by importing from the Monoid
companion object.
1 | scala> trait Monoid[A] { |
So, what we’ve done is create a general-purpose sum
function that works as long as there is a corresponding implicit monoid within scope. This is made possible by using ad-hoc polymorphism. I’ll cover ad-hoc polymorphism briefly in this post and defer providing a detailed explanation for a later post.
Ad-hoc Polymorphism
Ad-hoc polymorphism is a type of polymorphism in which polymorphic functions are invoked depending on the different argument types. One way to implement ad-hoc polymorphism that we already know about is function overloading where a different “version” of the function is invoked depending on the type of arguments. This is what we’ve done in the post where there is only a single function but an implementation is provided for different types. In other words, sum
only knows how to be invoked but the behavior is provided by monoids. Another way to implement ad-hoc polymorphism is coercion where the argument is converted into a type which the function expects.
So, by using ad-hoc polymorphism, Scalaz is able to provide general-purpose functions over existing types. Ad-hoc polymorphism is flexible in the sense that it lets you extend even those classes for which you do not have access to the source code i.e. classes from other libraries, etc.