All Articles

Typeclasses and Datatypes

A typeclass is a set functions that a datatype must implement when the datatype in context is an instance of said typeclass.

To make use of Typeclasses, the concept of Datatypes must first be introduced.

What is a Datatype?

A datatype is a kind of variable which can be used in a programming language. In most cases, built-in datatypes include: Integer, Bool, Char, etc.

In Haskell, a developer may implement their own datatype by using the data keyword.

-- Person has a name and age
data Person = Person [Char] Integer deriving Show

In this example the deriving Show fragment of the datatype definition means that the Person adheres to the Show typeclass. We will discuss later in the post.

Person is both a type and a constructor. A constructor is a function which creates instances of a type. Another way of defining the Person datatype could have been:

-- Person has a name and age
data Person = MakePerson [Char] Integer deriving Show

There are no rules which enforce the name of the constructor, it is up to the developer.

In this case, Haskell knows of the type Person but Person cannot be used as a variable. You would have to use MakePerson instead. There are no rules enforcing the name of the constructor.

With this knowledge of datatypes, typeclasses can now be discussed.

Using a Built-in Typeclass

Datatypes in Haskell that compare with one another, are instances of the equality typeclass. Internally, it is defined in the following way.

class Eq a  where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

The class keyword defines a typeclass name and the set of functions a datatype must implement. Any externally defined datatype must conform to Eq in order to be compared with == and /= operators.

-- Person has name and age
data Person = Person [Char] Integer deriving Show

instance Eq Person where
    (==) person1 person2 =
        let (Person name1 age1) = person1
            (Person name2 age2) = person2
        in (name1 == name2) && (age1 == age2)

    (/=) person1 person2 = not (person1 == person2)

Using the instance keyword, Person can implement its own versions of (==) and (/=). Person variables can now be compared, just like the built-in types.

-- test subjects --
p1 = Person "Eric" 23
p2 = Person "Eric" 23
p3 = Person "John" 23
p4 = Person "John" 36

-- test results --
result1 = p1 == p2 -- True
result2 = p1 == p3 -- False
result3 = p3 /= p4 -- True

Likewise, Person can become an instance of Show, and deriving Show can be omitted.

data Person = Person [Char] Integer

instance Show Person where
    show p1 =
        let (Person name age) = p1
        in "Hi, " ++ name

show (Person "Eric" 23) -- Hi, Eric

Defining a Typeclass

External typeclasses can be defined in Haskell and be used to enforce the implementation of functions on datatypes.

-- Person has name, age, hair color as string, eyes as a string
data Person = Person [Char] Integer String String deriving Show

class PhysicalFeatures a where
  hairColor :: a -> String -> Bool
  eyeColor :: a -> String -> Bool

As before, the datatype Person can now adhere to the PhysicalFeatures typeclass by using the instance keyword.

instance PhysicalFeatures Person where
  hairColor p1 hairColor =
    let (Person _ _ personHair _) = p1
    in personHair == hairColor

  eyeColor p1 eyeColor =
    let (Person _ _ _ personEye) = p1
    in personEye == eyeColor

Finally, instances of Person can use functions defined in the PhysicalFeatures typeclass.

-- test subjects --
p1 = Person "Eric" 23 "Brown" "Brown"
p2 = Person "Eric" 23 "Brown" "Green"
p3 = Person "John" 23 "Blonde" "Blue"
p4 = Person "John" 36 "Red" "Hazel"

-- check physical features --
result1 = hairColor p1 "Brown" -- True
result2 = hairColor p2 "Blonde" -- False
result3 = eyeColor p3 "Brown" -- False
result4 = eyeColor p4 "Hazel" -- True

Summary

In Haskell, typeclasses can be used to define the functionality of a type. A typeclass is defined using the class keyword. Types are defined using the data keyword, and they become instances of a typeclass by using the instance keyword.

Published 2 Dec 2015