Before we dive into type classes, let’s introduce the last remaining built-in datatype in Haskell: the tuple. Tuples or pairs (or triples, quadruples, etc) are a way of bundling a couple of values of different types together. You can think of tuples as fixed-length lists (just like Python’s tuples). Unlike lists, each element in the tuple can have a different type. The types of the elements are reflected in the type of the tuple. Here are some examples of tuple types and values:
To get values out of tuples, you can use the functions
fst :: (a, b) -> a snd :: (a, b) -> b
You can also pattern match on tuples. This is often the most convenient
way, and also works for tuples of larger sizes. The
functions work only on pairs.
Tuples are very useful in combination with lists. Here are some examples
partition functions from the
-- two lists to list of pairs zip :: [a] -> [b] -> [(a, b)] -- list of pairs to pair of lists unzip :: [(a, b)] -> ([a], [b]) -- elements that satisfy and don't satisfy a predicate partition :: (a -> Bool) -> [a] -> ([a], [a])
zip [1,2,3] [True,False,True] ==> [(1,True),(2,False),(3,True)] unzip [("Fred",1), ("Jack",10), ("Helen",13)] ==> (["Fred","Jack","Helen"],[1,10,13]) partition (>0) [-1,1,-4,3,2,0] ==> ([1,3,2],[-1,-4,0])
Here’s an example of pattern matching on tuples:
swap :: (a,b) -> (b,a) swap (x,y) = (y,x)
Here’s an example of pattern matching on tuples and lists at the same time:
-- sum all numbers that are paired with True sumIf :: [(Bool,Int)] -> Int sumIf  = 0 sumIf ((True,x):xs) = x + sumIf xs sumIf ((False,_):xs) = sumIf xs
sumIf [(True,1),(False,10),(True,100)] ==> 101
Consider the functions
sumNumbers :: [Int] -> Int,
myMaximum :: [Int] -> Int, and
countNothings :: [Maybe a] -> Int
sumNumbers :: [Int] -> Int sumNumbers  = 0 sumNumbers (x:xs) = x + sumNumbers xs myMaximum :: [Int] -> Int myMaximum  = 0 myMaximum (x:xs) = go x xs where go biggest  = biggest go biggest (x:xs) = go (max biggest x) xs countNothings :: [Maybe a] -> Int countNothings  = 0 countNothings (Nothing : xs) = 1 + countNothings xs countNothings (Just _ : xs) = countNothings xs
They have one common characteristic. They take a list and produce a value that depends on the values of the elements in the given list. They “crunch” or fold a list of many values into a single value.
Prelude has a function called
foldr, which performs a right
associative fold over a
Foldable data type. We’ll learn more about
Foldable soon. At this point, it suffices to think of lists, so we
foldr :: (a -> b -> b) -> b -> [a] -> b foldr f y  = y foldr f y (x:xs) = f x (foldr f y xs)
What this definition says, is that for an empty list
 :: [a],
foldr returns the default value
y :: b. For any other list
x : xs,
x and the result of
foldr f y xs
(i.e. folding over the rest of the list). It’s a simple definition by
In other words,
foldr calls its argument function
f repeatedly with
- The first argument is the current element from the list.
- The second argument is what
freturned for the rest of the list.
Consider the list
foldr (+) 0 [1,2,3] evaluates as follows:
foldr (+) 0 [1,2,3] ==> foldr (+) 0 (1:2:3:) ==> 1 + (foldr (+) 0 (2:3:)) ==> 1 + (2 + (foldr (+) 0 (3:))) ==> 1 + (2 + (3 + (foldr (+) 0 ))) ==> 1 + (2 + (3 + 0))
The result can be thought of as a tree:
One way to think about
foldr f y xs is that it replaces the
y. In this case,
0. If you write out how
sumNumbers [1,2,3] behaves, you’ll
notice that it performs the same computation as
foldr (+) 0 [1,2,3]
does! More generally:
sumNumbers xs == foldr (+) 0 xs
Those more experienced with math may notice that we can prove this claim
by induction: Firstly,
sumNumbers  ==> 0 and
foldr (+) 0  ==> 0, so in the base case
sumNumbers  == foldr (+) 0 . Next, we may assume as our induction
sumNumbers xs == foldr (+) 0 xs for any list
Then, for the list
x:xs, we have
sumNumbers (x:xs) ==> x + sumNumbers xs. Hence,
foldr (+) 0 (x:xs) ==> x + foldr (+) 0 xs ==> x + sumNumbers xs by
induction hypothesis. Therefore, by induction, the equation holds.
You don’t need to read, write, or understand induction proofs in this course, but perhaps it is reassuring to know that properties and equalities of functions in Haskell can be (in principle) analysed mathematically, because Haskell is such a nice language. (Equalities and properties can be analysed in any programming language, but for Haskell, this analysis is especially convenient because Haskell is pure.)
Another folding example is the
map g xs = foldr helper  xs where helper y ys = g y : ys
To see why this works, consider what
foldr helper  [x1,x2,..,xn]
helper x xs ==> g x : xs for every
xs, we get
The resulting list,
[ g x1, g x2, g x3, ..., g xn ], is then exactly
what we would have gotten with
map g xs. (This could have been also
proved by induction as we did for
sumNumbers.) The lesson to take away
is that folding is a particular, yet quite general, way to apply some
transformation recursively into some structure (e.g. a list).
How can Haskell’s
+ work on both
Doubles? Why can I
compare all sorts of things with
==? We’ve briefly mentioned
constrained types earlier. Let’s see what they really mean. Let’s look
at the types of
(==) :: (Eq a) => a -> a -> Bool
(Eq a) => a -> a -> Bool means: for all types
belong to the class
Eq, this is a function of type
a -> a -> Bool.
That is, if the type
a is a member of the class
Eq, you can give two
values of type
== and get a
(+) :: (Num a) => a -> a -> a
Similarly, the type
(Num a) => a -> a -> a means: for all types
that belong to the class
Num, this is a function of type
a -> a -> a. That is, you can give two values of the same type
+ and get out a third value of type
a, as long as
a is a member of
Eq are type classes. A type class is a way to group
together types that support similar operations.
Note! A type class is a collection of types. It doesn’t have much to
do with the classes of object oriented programming! In some situations,
type classes can act like interfaces in object oriented programming.
Unfortunately the functions in a type class are often called methods,
adding to the confusion.
PS. remember how using type variables for polymorphism was called parametric polymorphism? The fancy word for what type classes achieve is ad-hoc polymorphism. The difference is that with parametric polymorphism the function (e.g.
head) has the same implementation for
all types, whereas with ad-hoc polymorphisms there are multiple
== on numbers and strings).
When you’re working with a concrete type (not a type variable), you can
just use type class functions (in this case,
f :: (Int -> Int) -> Int -> Bool f g x = x == g x
Of course, if the type in question isn’t a member of the right class, you get an error. For example:
addTrue :: Bool -> Bool addTrue b = b + True
error: • No instance for (Num Bool) arising from a use of ‘+’ • In the expression: b + True In an equation for ‘addTrue’: addTrue b = b + True
However in a polymorphic function, you need to add type constraints. This doesn’t work:
f :: (a -> a) -> a -> Bool f g x = x == g x
Luckily the error is nice:
error: • No instance for (Eq a) arising from a use of ‘==’ Possible fix: add (Eq a) to the context of the type signature for: f :: (a -> a) -> a -> Bool • In the expression: x == g x In an equation for ‘f’: f g x = x == g x
To signal that
f only works on types that are members of the
class, we add a type constraint
(Eq a) => to the type annotation.
f :: (Eq a) => (a -> a) -> a -> Bool f g x = x == g x
If you don’t have a type annotation, type inference can provide the constraints!
Prelude> let f g x = x == g x Prelude> :type f f :: (Eq a) => (a -> a) -> a -> Bool
You can also have multiple constraints:
bothPairsEqual :: (Eq a, Eq b) => a -> a -> b -> b -> Bool bothPairsEqual x1 x2 y1 y2 = x1 == x2 && y1 == y2
Standard Type Classes
Here are some standard Haskell type classes you should know about.
We already saw the
Eq class for equality comparisons. Here are the
basic operations of the
Eq class and some examples of their use. As
you can see pretty much all the types we’ve seen so far, except for
functions, are members of
(==) :: Eq a => a -> a -> Bool (/=) :: Eq a => a -> a -> Bool
Prelude> 1 == 2 False Prelude> 1 /= 2 True Prelude> "Foo" == "Bar" False Prelude> [[1,2],[3,4]] == [[1,2],[3,4]] True Prelude> (\x -> x+1) == (\x -> x+2) <interactive>:5:1: error: • No instance for (Eq (Integer -> Integer)) arising from a use of ‘==’ (maybe you haven't applied a function to enough arguments?) • In the expression: (\ x -> x + 1) == (\ x -> x + 2) In an equation for ‘it’: it = (\ x -> x + 1) == (\ x -> x + 2)
There are some other useful functions that use the
Eq class, like
nub from the module
Prelude> import Data.List Prelude Data.List> :t nub nub :: Eq a => [a] -> [a] Prelude Data.List> nub [3,5,3,1,1] -- eliminates duplicates [3,5,1]
Ord class is for ordering (less than, greater than). Again, here
are the basic operations and some examples of their use. Note the new
Ordering type. It has values
LT for “less than”,
EQ for “equal”
GT for “greater than”.
compare :: Ord a => a -> a -> Ordering (<) :: Ord a => a -> a -> Bool (>) :: Ord a => a -> a -> Bool (>=) :: Ord a => a -> a -> Bool (<=) :: Ord a => a -> a -> Bool max :: Ord a => a -> a -> a min :: Ord a => a -> a -> a
Prelude> compare 1 1 -- 1 is EQual to 1 EQ Prelude> compare 1 3 -- 1 is Less Than 3 LT Prelude> compare 1 0 -- 1 is Greater Than 0 GT Prelude> min 5 3 3 Prelude> max 5 3 5 Prelude> "aardvark" < "banana" -- alphabetic comparison True Prelude> [1,2,3] > [2,5] -- similar to strings False Prelude> [1,2,3] > [1,1] True
When we can compare values, we can also sort lists of them. The function
Data.List works on all types that belong to the
Prelude> import Data.List Prelude Data.List> :t sort sort :: Ord a => [a] -> [a] Prelude Data.List> sort [6,1,4,8,2] [1,2,4,6,8] Prelude Data.List> sort "black sphinx of quartz" " aabcfhiklnopqrstuxz"
As a last example, let’s sort a list of lists according to length. We’ll need two helper functions:
-- from the module Data.Ord -- compares two values "through" the function f comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering comparing f x y = compare (f x) (f y) -- from the module Data.List -- sorts a list using the given comparison function sortBy :: (a -> a -> Ordering) -> [a] -> [a]
Now the implementation of
sortByLength is straightforward:
-- sorts lists by their length sortByLength :: [[a]] -> [[a]] sortByLength = sortBy (comparing length)
sortByLength [[1,2,3],[4,5],[4,5,6,7]] ==> [[4,5],[1,2,3],[4,5,6,7]]
Num class contains integer arithmetic:
(+) :: Num a => a -> a -> a (-) :: Num a => a -> a -> a (*) :: Num a => a -> a -> a negate :: Num a => a -> a -- 0-x abs :: Num a => a -> a -- absolute value signum :: Num a => a -> a -- -1, 0, or 1 fromInteger :: Num a => Integer -> a
Num also shows up in the types of integer literals:
Prelude> :t 12 12 :: Num p => p
This means that a literal like
12 can be interpreted as a member of
any type implementing
Num. When GHC reads a number literal like,
it produces code that corresponds to
Prelude> 1 :: Int 1 Prelude> 1 :: Double 1.0 Prelude> fromIntegral 1 :: Double 1.0
Integral is the class of types that represent whole numbers, like
Integer. The most interesting functions are
for integer division and remainder. All types that belong to
also belong to
div :: Integral a => a -> a -> a mod :: Integral a => a -> a -> a
Fractional is the class for types that have division. All types that
Fractional also belong to
(/) :: Fractional a => a -> a -> a
Floating contains some additional operations that only make sense for
floating point numbers. All types that belong to
Floating also belong
Fractional (and to
sqrt :: Floating a => a -> a sin :: Floating a => a -> a
Read classes are for the functions
that convert values to and from Strings.
show :: Show a => a -> String read :: Read a => String -> a
Prelude> show 3 "3" Prelude> read "3" :: Int 3 Prelude> read "3" :: Double 3.0
As you can see above, you often need to use a type annotation with
read so that the compiler can choose the right implementation.
One more thing! You might remember that it was mentioned earlier that
the type of
[a] -> Int but something more general.
Let’s have a look:
Prelude> :t length length :: Foldable t => t a -> Int
This type looks a bit different than the ones we’ve seen before. The
t has an argument
a. We’ll look at type classes like
this in more detail later, but here’s a crash course.
Foldable represents is types that you can fold over. The true
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
We’ve succesfully used the fact that lists are
Foldable since we’ve
managed to use
foldr on lists. However,
Maybe is also
Foldable instance for
Maybe just pretends that
Maybe a are like lists of length 0 or 1:
foldr (+) 1 Nothing ==> 1 foldr (+) 1 (Just 3) ==> 4 length Nothing ==> 0 length (Just 'a') ==> 1
Haskell libraries tend to have pretty good docs. We’ve linked to docs via Hackage (https://hackage.haskell.org) previously, but it’s important to know how to find the docs by your self too. The tool for generating Haskell documentation is called Haddock so sometimes Haskell docs are referred to as haddocks.
Hackage is the Haskell package repository (just like
PyPI for Python, Maven Central for Java or
packages, it hosts documentation for them. Most of the modules that we
use on this course are in the package called
base. You can browse the
docs for the base package at http://hackage.haskell.org/package/base.
When you’re not quite sure where the function you’re looking for is,
Hoogle (https://hoogle.haskell.org/) can help. Hoogle is a search
engine for Haskell documentation. It is a great resource when you need
to check what was the type of
foldr or which packages contain a
In summary, here are the main ways of reading Haskell library documentation:
- If you know the name of the package you browse the docs via https://hackage.haskell.org/.
- If you know the name of the function you can find it using https://hoogle.haskell.org/.
What is the type of
swap . swap?
(a, b) -> (a, b)
(a, b) -> (b, a)
a -> a
What is the type of
\f g x -> (f x, g x)?
(a -> b) -> (c -> d) -> (a,c) -> (b, d)
(a -> b) -> (a -> c) -> a -> (b, c)
(a -> b) -> (b -> a) -> a -> (b, a)
What is the type of
\t -> (fst . fst $ t, (snd . fst $ t, snd t))?
(a, (b, c)) -> (a, (b, c))
(a, (b, c)) -> ((a, b), c)
((a, b), c) -> (a, (b, c))
What does the function
foldr (\x xs -> xs ++ [x])  do?
- It doesn’t change its input list at all
- It changes the associativity of a list from left to right
- It reverses its input list
What does the function
foldr (\(x, y) zs -> x : y : zs)  do?
- It turns a list of pairs into a pair of lists
- It turns a pair of lists into a list of pairs
- It turns a list of pairs into a list of elements
What is the type of
foldr (\n b -> n == 3 && b)?
(Foldable t, Eq a, Num a) => Bool -> t a -> Bool
(Foldable t, Eq a, Num a, Bool b) => b -> t a -> b
(Foldable t, Eq a, Num a) => Bool -> [ a ] -> Bool
What is the type of
\x -> case x of (True, "Foo") -> show True ++ "Foo"?
Either Bool String -> String
(Bool, String) -> String
Show a => (Bool, String) -> a
This reading was originally written by Joel Kaasinen and John Lång in their open source textbook. The book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License, which allows users to copy, modify, and distribute the book.
This reading was modified by Titus H. Klinge in 2021 and presented above under the same license in order to better serve the students of this course.