Road to Haskeller #10 - Typeclasses

Last Edited: 6/22/2024

The blog post introduces an important concept of typeclasses in Haskell.

Haskell & Typeclasses

Typeclasses

We have been talking extensively about types, but we have avoided using Num, Ord, and Eq that VSCode often suggests. This is because they are typeclasses, not types, and I wanted to discuss typeclasses in a separate article. Typeclasses are like interfaces, if you are familiar with object-oriented programming, which define some behavior of types that belong to that typeclass. The + function is an example of a behavior enforced by the Num typeclass.

ghci> :t (+)
(+) :: Num a => a -> a -> a

The type of the + function indicates that any type in the Num typeclass could be inputs and an output. Using the :info flag, we can examine other functions defined in a typeclass.

ghci> :info Num
class Num a where
 (+) :: a -> a -> a
 (-) :: a -> a -> a
 (*) :: a -> a -> a
 negate :: a -> a
 abs :: a -> a
 signum :: a -> a
 fromInteger :: Integer -> a
 {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
        -- Defined in ‘GHC.Num’
instance Num Double -- Defined in ‘GHC.Float’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’

Notice here that only the types are defined for those functions. This is because the actual logic for different types that belong to this typeclass might be different. The part where it says MINIMAL highlights the functions that are necessary to make a valid instance of that typeclass. The next part lists some predefined types that belong to the Num typeclass. Let's see the typeclasses that are notoriously used.

ghci> :info Show
class Show a where
  showPrec :: Int -> a -> ShowS
  show :: a -> String
  showList :: [a] -> ShowS
  {-# MINIMAL ShowPrec | show #-}

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

Most types belong to Show so that the output of a function can be presented as a string in GHCi, and Eq is responsible for testing equality.

Usage

When does this become useful? Let's look at how typeclasses can be used in an example of a Temperature class.

data Temperature = C Float | F Float 
--- The C and F constructors are for Celsius and Fahrenheit respectively
 
--- use Eq to enforce rules
instance Eq Temperature where
  (==) (C n) (C m) = n == m
  (==) (F n) (F m) = n == m
  (==) (C n) (F m) = (1.8*n - 3.2) == m
  (==) (F n) (C m) = (1.8*m - 3.2) == n
--- Pattern matching

We can see that by using instance Eq and defining logic for the Temperature datatype, we can check for equality even when we have different temperature scales (Celsius vs Fahrenheit).

Deriving Typeclasses

If you do not want to have special rules for a datatype like Temperature and just want to make the datatype be an instance of Eq for a simple equality check, we can use the following shorter syntax.

data Temperature = C Float | F Float
  deriving(Eq)

When you use the deriving syntax, Haskell can automatically generate the simplest functions necessary for it to be a valid instance of the specified typeclass(es). Though the above is easy and works for the most part, there are cases where you might want to avoid using this syntax. Let's see what Haskell automatically generated for Temperature from the above.

(==) (C n) (C m) = n == m
(==) (F n) (F m) = n == m
(==) _ _ = False

We can see that Haskell only considered the cases where value constructors are the same because it does not know the semantic meaning of the symbols of value constructors.

Exercises

This is an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.

Resources