Lecture 18 Type Class Instances

Joseph Haugh

University of New Mexico

Free Recall

Review

Given the following data type:

data Foo a = Bar Int Double [String] | Baz Int (Int, Int)

What type does Bar have?

Review

Given the following data type:

data Foo a = Bar Int Double [String] | Baz Int (Int, Int)

What type does Bar have?

Int -> Double -> [String] -> Foo a

What type does Baz have?

Review

Given the following data type:

data Foo a = Bar Int Double [String] | Baz Int (Int, Int)

What type does Bar have?

Int -> Double -> [String] -> Foo a

What type does Baz have?

Int -> (Int, Int) -> Foo a

Review

Given the following data type, function, and instantiation:

data Tree a = Leaf | Node (Tree a) a (Tree a)

foo :: Tree a -> [a]
foo Leaf           = []
foo (Node lt a rt) = a : a : (foo lt ++ foo rt)

t :: Tree Int
t = Node (Node Leaf 5 (Node Leaf 6 Leaf)) 3 (Node Leaf 4 Leaf)

What is the value of the following expression?

> foo t

Review

Given the following data type, function, and instantiation:

data Tree a = Leaf | Node (Tree a) a (Tree a)

foo :: Tree a -> [a]
foo Leaf           = []
foo (Node lt a rt) = a : a : (foo lt ++ foo rt)

t :: Tree Int
t = Node (Node Leaf 5 (Node Leaf 6 Leaf)) 3 (Node Leaf 4 Leaf)

What is the value of the following expression?

> foo t
[3,3,5,5,6,6,4,4]

Review

Given the following data type, function, and instantiation:

data Tree a = Leaf | Node (Tree a) a (Tree a)

baz :: Num a => Tree a -> Tree (a, a)
baz Leaf           = Leaf
baz (Node lt a rt) = Node (baz lt) (a + 1, a * 2) (baz rt)

t :: Tree Int
t = Node (Node Leaf 5 (Node Leaf 6 Leaf)) 3 (Node Leaf 4 Leaf)

What is the value of the following expression?

> baz t

Review

Given the following data type, function, and instantiation:

data Tree a = Leaf | Node (Tree a) a (Tree a)

baz :: Num a => Tree a -> Tree (a, a)
baz Leaf           = Leaf
baz (Node lt a rt) = Node (baz lt) (a + 1, a * 2) (baz rt)

t :: Tree Int
t = Node (Node Leaf 5 (Node Leaf 6 Leaf)) 3 (Node Leaf 4 Leaf)

What is the value of the following expression?

> baz t
Node (Node Leaf (6, 10) (Node Leaf (7, 12) Leaf)) (4, 6) (Node Leaf (5, 8) Leaf)

Does that actually work thought the way it is shown??

Let’s try it…

Instance Declarations

We get this error:

:7:1: error:
    • No instance for (Show (Tree (Int, Int)))
        arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

What does that mean?

It is telling us that Haskell doesn’t know how to print or Show this value.

How do we fix this?

We need to instance Tree into Show.

Show Typeclass

The Show typeclass asks us to define a single function:

show :: a -> String

Where the a in this case is our Tree a.

Show Typeclass

instance Show a => Show (Tree a) where
    show Leaf = "Leaf"
    show (Node lt a rt) =
        "Node (" ++ show lt ++ ") " ++
        show a ++
        " (" ++ show rt ++ ")"

or

instance Show a => Show (Tree a) where
    show Leaf = "Leaf"
    show (Node lt a rt) = concat [
        "Node (", show lt, ") ",
        show a,
        " (", show rt, ")"]

Eq Typeclass

Let’s now take a step back and look at the Eq typeclass.

This typeclass defines what it means for a datatype to define equality.

Its declaration looks like this:

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

    x /= y = not (x == y)

This means that both (==) and (/=) take 2 as and return a Bool and that (/=) can be derived from (==).

What is a here?

It is whatever type you are instancing into Eq.

Defining An Instance

For example if we were to instance Bool into Eq it might look like this:

instance Eq Bool where
    -- (==) Bool -> Bool -> Bool
    False == False = True
    True  == True  = True
    _     == _     = False

Here a is equal to Bool.

Notice we do not have to define (/=) since it can be derived from (==).

:i Command

Recall that :i will tell us all this information within ghci.

> :i Eq
...
class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool
  {-# MINIMAL (==) | (/=) #-}
...

MINIMAL is the least amount of functions we need to define in order to be a member of the class. In this case we can define (==) or (/=).

Exercise: Instance Direction Eq

Given the following datatype:

data Direction = North | East | South | West

Give a minimal instance for Direction into Eq

instance Eq Direction where
    North == North = True
    East  == East  = True
    South == South = True
    West  == West  = True
    _     == _     = False

Ord Typeclass

Recall the definition of the Ord typeclass:

class Eq a => Ord a where
    (<), (<=), (>), (>=) :: a -> a -> Bool
    max, min :: a -> a -> a

    min x y | x <= y    = x
            | otherwise = y

    max x y | x <= y    = y
            | otherwise = x

    MINIMAL compare | (<=)

Here the typeclass has an Eq constrain on a.

This means that a type must already be a member of Eq in order to be a member of Ord.

Exercise: Instance Direction Ord

Instance Direction into Ord where North < East < South < West

data Direction = North | East | South | West

class Eq a => Ord a where
    (<), (<=), (>), (>=) :: a -> a -> Bool
    max, min :: a -> a -> a

    min x y | x <= y    = x
            | otherwise = y

    max x y | x <= y    = y
            | otherwise = x

    -- {MINIMAL compare | (<=)}

Exercise: Instance Direction Ord

Instance Direction into Ord where North < East < South < West

data Direction = North | East | South | West

instance Ord Direction where
    North <= _      = True
    _     <= West   = True
    East  <= South  = True
    d1    <= d2
        | d1 == d2  = True
        | otherwise = False

Derived Instances

Boring right?

When something is boring try to automate it!

Haskell already does automate simple instances like this!

We could have just done this:

data Direction = North | East | South | West
    deriving (Eq, Ord)

Done!

Manual Instances

There are times where you will want to create your own custom instance.

For example what if we had this data type:

data ListInt = Empty | ConsLI Int ListInt

and we wanted to instance it into Eq and Ord but for the Ord instance we will say that a list is greater than another list if its sum is greater.

Manual Instances

data ListInt = Empty | ConsLI Int ListInt
    deriving (Eq)

sumLI :: ListInt -> Int
sumLI Empty         = 0
sumLI (ConsLI x xs) = x + sumLI xs

instance Ord ListInt where
    Empty <= _ = True
    li1@(ConsLI x xs) <= li2@(ConsLI y ys) =
        sumLI li1 <= sumLI li2
    _ <= _ = False
> (ConsLI 1 (ConsLI 2 (ConsLI 3 Empty))) <= (ConsLI 7 Empty)
True
> (ConsLI 1 (ConsLI 2 (ConsLI 3 Empty))) <= (ConsLI 6 Empty)
True
> (ConsLI 1 (ConsLI 2 (ConsLI 3 Empty))) <= (ConsLI 5 Empty)
False

Manual Instances

Could we do this for our more general List data type?

data List a = Nil | Cons a (List a)

Let’s try:

data List a = Nil | Cons a (List a)
    deriving (Eq)

sumL :: Num a => List a -> a
sumL Nil         = 0
sumL (Cons x xs) = x + sumL xs

instance (Num a, Eq a, Ord a) => Ord (List a) where
    Nil <= _ = True
    l1@(Cons x xs) <= l2@(Cons y ys) =
        sumL l1 <= sumL l2
    _ <= _ = False

Manual Instances

> (Cons 1 (Cons 2 (Cons 3 Nil))) <= (Cons 7 Nil)
True
> (Cons 1 (Cons 2 (Cons 3 Nil))) <= (Cons 5 Nil)
False
> (Cons 'a' Nil) <= (Cons 'b' Nil)
• No instance for (Num Char) arising from a use of ‘<=’
• In the expression: (Cons 'a' Nil) <= (Cons 'b' Nil)
  In an equation for ‘it’: it = (Cons 'a' Nil) <= (Cons 'b' Nil)