kuniga.me > NP-Incompleteness > Haskell – Typeclasses
04 Dec 2011
In this post I will comment the typeclass structure in Haskell. My main reference is chapter 6 of Real World Haskell [1].
Since Haskell supports parameterized types, we may want to define functions that accept parameters of such types. However, the function implementation will likely be different for each specific type “instance”.
Typeclass is a framework to provide this functionality. The basic syntax is as follows:
Despite the class
keyword, a typeclass is quite different of a class that we are used to in object-oriented languages.
One case where typeclass is useful is in implementing the equality operator. Suppose we have two types Boolean
and RGBColor
:
We can define a typeclass containing an equality function, which receives two parameters of the same type parameterized a
, and returns True
if they are equal or False
otherwise.
Then we can define an implementation for any type we’re interested in, using the instance
construct. The syntax is similar to a typeclass, but we replace the parameterized type by the actual type and define the function implementation. For the case of Boolean
and RGBColor
, we have:
We can also provide default implementations for a typeclass by defining the implementation in the class structure. During runtime, the implementation used is always the most specific one.
For example, in the typeclass Compare
, we can define the function different
and define default implementations for it and also for the function equals
:
In defining Boolean
and RGBColor
, we added deriving(Show)
. Essentially Show
is a typeclass from the standard library. The function show
in this typeclass transforms a generic type into a String
.
Doing some tests in the terminal (ghci):
Note that the command :type
uses a different syntax to say that the function show belongs to the typeclass Show
.
We can write our own implementation for RGBColor
instead of using the default. To do this, we remove the deriving(Show)
statement and add:
According to [2], the derive
functionality can only be used with a specific set of built-in typeclasses.
Imagine that we want to implement the equals()
function for a list of Boolean
s. The first attempt would be:
In the above code, undefined
is a wildcard type that does not cause compilation errors due to type mismatch, but does cause a runtime error. This technique of using undefined
’s is interesting for compiling intermediate versions of your code even without implementing all the functions.
Anyway, we will see that such a code snippet leads to the following compilation error:
There are several ways to get around this error. One is to define a unique function for lists, which in the case of the function equals()
can be called equalList()
:
The advantage in this case is that the default implementation should work for most cases, since in general the test for equality between two lists consists of checking if all elements in both are equal.
In case you want to define the implementation only for a list of a particular type (eg [Boolean]
), you can encapsulate that list in a structure called newtype.
The newtype is similar to an algebraic data type (keyword data
), with some differences such as the newtype requiring exactly one data constructor and the type of a newtype is resolved at compile time (unlike data
) and therefore its use does not cause overhead in program execution [1]. So we could do:
Now we can implement the function, with the inconvenience of wrapping the data of the new type with Wrapper
:
A good reference for these and other alternatives is the haskell wiki [3].
I found this subject very complicated! To make matters worse, it seems that the book Real World Haskell I’m basing on, wrote chapters 5 and 6 very badly, giving very complex examples (maybe it’s the “real world” vision, but this is definitely not didactic), confusing and boring. There are several reviews that share this opinion.
Despite everything, I will continue my studies with this book, even if I only use the topics to guide me and look for explanations in references like [2] and [3].