Haskell Typeclasses - Basics
2019-05-31
In Haskell, typeclasses can be thought of as interfaces. There are programmer defined typeclasses and there are also the typeclasses defined in the Haskell base
libraries[1] such as Eq
, Show
, and Read
. Eq
, Show
, and Read
are some of the fundamental typeclasses in Haskell. They are defined just like any other typeclass. The class definition of Eq
for example[2] is:
Any type that is an instance of the Eq
typeclass has a (==)
function that can be used to test the equality of two values of that type. For example, lets create an instance for a Vec2D
type. Lets ask ourselves, “What does it mean for two 2D vectors to be equal?”:
-- Our type definition
data Vec2D = Vec2D { x :: Float, y :: Float }
-- Our Eq instance for Vec2D
instance Eq Vec2D where
(==) :: Vec2D -> Vec2D -> Bool
(==) (Vec2D x1 y1) (Vec x2 y2) = x1 == x2 && y1 == y2
Let’s take a look at our implementation of the Eq
instance for Vec2D
. First we pattern match on both Vec2D
values and extract their x and y values. We then test the equality of the x and y values. If the x value of the first vector (x1
) and the x value of the second vector (x2
) are equal, and the y value of the first vector (y1
) and the y value of the second vector (y2
) are equal, then the two vectors are equal. This makes sense because vectors in two dimensions are equal to each other when the dimensions of the two vectors are equal. Note the type signature for (==)
in the instance definition[3]. The polymorphic type a
in the Eq
typeclass definition has been “filled” in by the Vec2D
type.
We could have implemented the Eq
instance for Vec2D
differently. For example:
-- Our Eq instance for Vec2D
instance Eq Vec2D where
(==) :: Vec2D -> Vec2D -> Bool
(==) (Vec2D x1 y1) (Vec x2 y2) = x1 == y2 && x2 == y1
We’ve defined the equality of two Vec2D
values differently this time. This definition doesn’t really make sense though. There are usually many ways to implement an instance of a typeclass for any given type, but not all of them may be correct.
Let’s try to implement the add
operation under a VecOps
typeclass for values of type Vec2D
.
Once again, lets think about what it means to add 2D vectors together:
instance VecOps Vec2D where
add :: Vec2D -> Vec2D -> Vec2D
add (Vec x1 y1) (Vec x2 y2) = Vec (x1 + x2) (y1 + y2)
sub = undefined
dot = undefined
Here, we pattern match on the two vectors passed into the function like last time. Then we construct a new Vec2D
that has x1 + x2
as an x value, and y1 + y2
as a y value. We have successfully defined add
for values of type Vec2D
.
As an exercise, try to implement the rest of the VecOps
methods for Vec2D
or try to implement them for a Vec3D
type with three dimensions.
Footnotes:
[1] http://hackage.haskell.org/package/base-4.3.1.0/docs/src/GHC-Classes.html
This is the actual definition. I just simplified it a bit.
[2] http://hackage.haskell.org/package/base
The base
library for Haskell. I recommend looking through it if you like Haskell.
[3] Normally, type signatures are not allowed in instance definitions unless there is {-# LANGUAGE InstanceSigs #-}
at the top of the source file. I’m using the type signatures for educational purposes.