Chapter 3: Types and Classes

What is a Type?

A type is a name for a collection of related values. For example, in Haskell the basic type Bool contains the two logical values False and True

Type Errors

In [1]:
1 + False
<interactive>:1:1: error:
    • No instance for (Num Bool) arising from a use of ‘+’
    • In the expression: 1 + False
      In an equation for ‘it’: it = 1 + False

The + operator requires two numbers; but in the above expression we are trying to add 1 to False

Types in Haskell

If evaluating an expression e would produce a value of type t, then e has type t, written as

e :: t

  • Every well-formed expression has a type, which can be automatically calculated at compile time using an algorithm called type inference

  • All type errors are found at compile time, which makes programs safer and faster by removing the need for type checks at run time.

  • In GHCi, the :type command calculates the type of an expression, without evaluating it:

In [5]:
not False
Evaluate
Found:
not False
Why Not:
True
True
In [6]:
:type not False
not False :: Bool

Basic Types

  • Bool denoting logical values
  • Char denoting single characters
  • String denoting strings of characters
  • Int denoting integer numbers
  • Float denoting floating-point numbers

List Types

A list is a sequence of values of the same type

In [8]:
[False, True, False] :: [Bool]
['a','b','c','d'] :: [Char]
[False,True,False]
"abcd"

In general, [t] is the type of lists with elements of type t

NOTE

  • The type of a list says nothing about its length:
In [9]:
[False, True] :: [Bool]
[False, True, False] :: [Bool]
[False,True]
[False,True,False]
  • The type of the elements is unrestructed. For example, we can have a list of lists
In [11]:
[['a'],['b','c']] :: [[Char]]
["a","bc"]

Tuple Types

A tuple is a sequence of values of possibly different types

In [13]:
(False,True) :: (Bool,Bool)

(False,'a',True) :: (Bool,Char,Bool)
(False,True)
(False,'a',True)

In general

(t1,t2,…,tn) is the type of n-tuples whose ith components have type ti for any i in 1…n.

  • The type of a tuple encodes its size
In [16]:
(False,True) :: (Bool,Bool)

(False,True,False) :: (Bool,Bool,Bool)
(False,True)
(False,True,False)
  • The type of the components is unrestricted
In [15]:
('a',(False,'b')) :: (Char,(Bool,Char))

(True,['a','b']) :: (Bool,[Char])
('a',(False,'b'))
(True,"ab")

Function Types

A function is a mapping from values of one type to values of another type:

not :: Bool -> Bool

even :: Int -> Bool

In general, t1 -> t2 is the type of functions that map values of type t1 to values of type t2

In [5]:
not :: Bool -> Bool
not True = False
not False = True
In [7]:
even :: Int -> Bool
even x = (x `mod` 2) == 0

The argument and result types are unrestricted; Also, there can be any muber of arguments.

In [10]:
add :: (Int,Int) -> Int
add (x,y) = x + y
In [12]:
add (2, 3)
5
In [13]:
zeroto :: Int -> [Int]
zeroto n = [0..n]
In [14]:
zeroto 5
[0,1,2,3,4,5]

Curried Functions

Functions with multiple parameters can be converted to functions with one parameter using the technique called "Currying". This is possible because the return value of a function can be a function itself!

In [18]:
add' :: Int -> (Int -> Int)
add' x y = x + y
-- note that the two paramaters x and y are not written in tuple-form!
In [19]:
add' 3 4
7

add' takes an integer x as input and returns a function add' x. In turn, this function takes an iinteger y as input and returns the result x + y

Note

  • add and add' produce the same final result, but add takes its two arguments at the same time, whereas add' takes them one at a time
    add :: (Int,Int) -> Int
    add' :: Int -> Int -> Int
  • Functions that take their arguments one at a time are called curried functions, celebrating the work of Haskell Curry.
In [21]:
-- Another example of Curried Function:

mult :: Int -> (Int -> (Int -> Int))
mult x y z = x * y * z
In [22]:
mult 2 3 4
24

Here, mult takes input x and returns a function mult x, which in turn takes input y and returns a function mult x y, which finally takes as input z and returns the result x * y * z

Curried functions are more flexible that functions on tuples, because useful functions can often be made by partially applyying a Curried function. e.g.

add' 1 :: Int -> Int
take 5 :: [Int] -> [Int]
drop 5 :: [Int] -> [Int]
In [32]:
incr :: Int -> Int
incr = add' 1

take5 :: [Int] -> [Int]
take5 = take 5

drop5 :: [Int] -> [Int]
drop5 = drop 5
In [34]:
incr 5
first5 [0..7]
drop5 [0..7]
6
[0,1,2,3,4]
[5,6,7]

To avoid excessive parentheses when using Curried functions, we adopt the following Currying Conventions:

  • The arrow -> associates to the right; e.g.
Int -> Int -> Int -> Int

means

Int -> (Int -> (Int -> Int))
  • function application associates to the left; e.g.
mult x y z

means

((mult x) y) z

Unless tupling is explicitly required, all functions in Haskell are normally defined in Curried form.

Polymorphic Functions

A function is called polymorphic ("of many forms") if its type contains one or more type variables. For example,

In [12]:
len :: [a] -> Int
len [] = 0
len (_:xs) = 1 + len xs
In [15]:
len [1,2,3]  -- a is Int
len [False,True,True,False] -- a is Bool
len ["john","alice","bob","evan","zelle"] -- a id String
3
4
5

In the above function, len takes as input a list of values of type a and returns an Int. Type variables must begin with lower-case and typically we use a, b, c, etc.

Many of the functions defined in the standard prelude are ploymorphic. For example,

fst :: (a,b) -> a
head :: [a] -> a
take :: Int -> [a] -> [a]
zip :: [a] -> [b] -> [(a,b)]
id :: a -> a
In [18]:
fst (10,20)
fst ('a','b')
head [1,2,3,4]
head ["john","alice","bob","evan","zelle"]
take 2 ["john","alice","bob","evan","zelle"]
take 2 [1,2,3,4]
zip [1,2,3,4] ['a','b','c','d']
zip [True,False] ["John","Alice"]
id 4
id "aa"
10
'a'
1
"john"
["john","alice"]
[1,2]
[(1,'a'),(2,'b'),(3,'c'),(4,'d')]
[(True,"John"),(False,"Alice")]
4
"aa"

Overloaded Functions

A polymorphic function is called overloaded if its type contains one or more class constraints. e.g.

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

For any numeric type a, (+) takes two values of type a and returns a value of type a, i.e. the type of inputs and outputs of the + operator/function must come from a numeric type such a Int, Float etc.

Constrained type variables can be instantiated to any types that satisfy the constraints:

In [20]:
1 + 2  -- a is Int
1.0 + 2.0 -- a is Float
'a' + 'b' -- a is Char; not allowed!
3
3.0
<interactive>:1:1: error:
    • No instance for (Num Char) arising from a use of ‘+’
    • In the expression: 'a' + 'b'
      In an equation for ‘it’: it = 'a' + 'b'

Haskell has a number of type classes, including

Num - Numeric types

Eq - Equality types

Ord - Ordered types

For example

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

(==) :: Eq a => a -> a -> Bool

(<) :: Ord a => a -> a -> Bool
In [21]:
1 + 2
1.2 + 3.4
2 == 3
'a' == 'b'
3.5 == 3.6
'a' < 'b'
2 < 3
2.5 < 1.2
3
4.6
False
False
False
True
True
False

Hints and Tips

  • When defining a new function in Haskell, it is useful to begin by writing down its type.

  • Within a script, it is good practice to state the type of every new function defined.

  • When stating the types of polymorphic functions that use numbers, equality or orderings, take care to include the necessary class constraints.

Exercises

(1) What are the types of the following values:

['a','b','c']

('a','b','c')

[(False,'0'),(True,'1')]

([False,True],['0','1'])

[tail,init,reverse]
In [23]:
:type ['a','b','c']
['a','b','c'] :: [Char]
In [24]:
:type ('a','b','c')
('a','b','c') :: (Char, Char, Char)
In [25]:
:type [(False,'0'),(True,'1')]
[(False,'0'),(True,'1')] :: [(Bool, Char)]
In [26]:
:type ([False,True],['0','1'])
([False,True],['0','1']) :: ([Bool], [Char])
In [27]:
:type [tail,init,reverse]
[tail,init,reverse] :: forall a. [[a] -> [a]]

(2) What are the types of the following functions:

second xs = head (tail xs)
swap (x,y) = (y,x)
pair x y = (x,y)
double x = x * 2
palindrome xs = reverse xs == xs
twice f x = f (f x)
In [30]:
second xs = head (tail xs)
:type second
second :: forall a. [a] -> a
In [31]:
swap (x,y) = (y,x)
:type swap
swap :: forall b a. (b, a) -> (a, b)
In [32]:
pair x y = (x,y)
:type pair
pair :: forall a b. a -> b -> (a, b)
In [33]:
double x = x * 2
:type double
double :: forall a. Num a => a -> a
In [34]:
palindrome xs = reverse xs == xs
:type palindrome
palindrome :: forall a. Eq a => [a] -> Bool
In [35]:
twice f x = f (f x)
:type twice
twice :: forall t. (t -> t) -> t -> t