CHAPTER 3 - TYPES AND CLASSES

BASIC CONCEPTS

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

  • A simple typing rule for function application:

f :: A -> B
e :: A
-----------
f e :: B
  • 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 [1]:
not False
Evaluate
Found:
not False
Why Not:
True
True
In [1]:
:type not
not :: Bool -> Bool
In [2]:
:type False
False :: Bool
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 fixed precision integer numbers
  • Integer denoting arbitray precision integer numbers
  • Float denoting floating-point numbers
  • Double denoting double-precision floating-point numbers

LIST TYPES

A list is a sequence of values of the same type

In [3]:
[False, True, False] :: [Bool]
['a','b','c','d'] :: [Char]
["One","Two","Three"]
[False,True,False]
"abcd"
["One","Two","Three"]

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 unrestricted. 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.

n > 1

The reason n=1 is not allowed is because it can confuse with parenthesised expressions. n is called the arity.

  • 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
In [3]:
-- Inductive definition of odd/even
even1 :: Int -> Bool
even1 0 = True
even1 1 = False
even1 n = odd1 (n-1)

odd1 :: Int -> Bool
odd1 0 = False
odd1 1 = True
odd1 n = even1 (n-1)
In [4]:
odd1 22
odd1 21
even1 22
even1 21
False
True
True
False

The argument and result types are unrestricted; Also, there can be any number 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 integer 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 applying 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 polymorphic. For example,

fst :: (a,b) -> a
head :: [a] -> a
take :: Int -> [a] -> [a]
zip :: [a] -> [b] -> [(a,b)]
id :: a -> a
In [1]:
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'
(*) :: Num a => a -> a -> a
negate :: Num a => a -> a
abs :: Num a => a -> a
In [3]:
2 * 2
2 * 2.5
negate 2.5
abs (-2.5)
4
5.0
-2.5
2.5
In [4]:
-- Numbers themselves are overloaded
:type 3
3 :: forall p. Num p => p

TYPE CLASSES

Haskell has a number of type classes, including

Eq - Equality types

Ord - Ordered types

Show - showable types

Read - readable types

Num - Numeric types

Integral - integral types

Fractional - fractional types

Eq

This class contains types whose values can be compared for equality and inequality using the following two methods:

(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
``Bool``, ``Char``, ``String``, ``Int``, ``Integer``, ``Float``, and ``Double`` are instances of ``Eq`` class, as are ``list`` and ``tuple`` types as long as their component types are instances of ``Eq``

But function types are not in Eq because it is not feasible to compare two functions for equality.

In [2]:
-- For example
False == False
'a' == 'b'
"abc" == "abc"
[1,2] /= [1,2,3]
('a',20) == ('a',30)
Redundant ==
Found:
False == False
Why Not:
not False
Redundant ==
Found:
False == False
Why Not:
not False
True
False
True
True
False

Ord

This class contains types that are instances of Eq re totally (linearly) ordered, and as such can be compared by the following 6 methods:

(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
min :: a -> a -> a
max :: a -> a -> a
`

Bool, Char, String, Int, Integer, Float, and Double are instances of Ord class, as are list and tuple types as long as their component types are instances of Ord

In [8]:
False < True
min 'a' 'b'
"elegant" < "elephant"
[1,2,3] < [1,2]
('a',2) < ('b',1)
('a',2) < ('a',1)
True
'a'
True
False
True
False

Show

This class contains types whose values can be converted into string of characters using the following function:

show :: a -> String

Bool, Char, String, Int, Integer, Float, and Double are instances of Show class, as are list and tuple types as long as their component types are instances of Show

In [10]:
show False
show 'a'
show 123
show [1,2,3]
show ('a',False)
"False"
"'a'"
"123"
"[1,2,3]"
"('a',False)"

Read

This class is dual to Show, and contains types whose values can be converted from strings of characters using the following method:

read :: String -> a

Bool, Char, String, Int, Integer, Float, and Double are instances of Read class, as are list and tuple types as long as their component types are instances of Read

In [17]:
read "False" :: Bool
read "123" :: Int
read "'a'" :: Char
read "[1,2,3]" :: [Int]
read "('a',False)" :: (Char,Bool)
not (read "False")
False
123
'a'
[1,2,3]
('a',False)
True

Num

This class contains types who values are numeric, and as such can be processed by the following six methods:

(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
(negate) :: a -> a
(abs) :: a -> a
(signum) :: a -> a

The basic types Int, Integer, Float, and Double are instances of Num

In [19]:
1 + 2
1.0 + 2.0
negate 3.0
abs (-3)
signum (-3)
3
3.0
-3.0
3
-1

Integral

This class contains types that are instances of the numeric class Num, but in addition whose values are integers, asnd as such support the methods of integer divison and integer remainder:

div :: a -> a -> a
mod :: a -> a -> a
In [21]:
7 `div` 2
7 `mod` 2
div 7 2
mod 7 2
3
1
3
1

Fractional

This class contains types that are instances of the numeric class Num, but in addition whose values are non-integral, and as such support the methids of fractional division and fractional reciprocation.

(/) :: a -> a -> a
recip :: a -> a
In [22]:
7.0 / 2
recip 2.0
3.5
0.5