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
1 + False
The +
operator requires two numbers; but in the above expression we are trying to add 1
to False
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:
not False
:type not False
Bool
denoting logical valuesChar
denoting single charactersString
denoting strings of charactersInt
denoting integer numbersFloat
denoting floating-point numbersA list is a sequence of values of the same type
[False, True, False] :: [Bool]
['a','b','c','d'] :: [Char]
In general, [t]
is the type of lists with elements of type t
NOTE
[False, True] :: [Bool]
[False, True, False] :: [Bool]
[['a'],['b','c']] :: [[Char]]
A tuple is a sequence of values of possibly different types
(False,True) :: (Bool,Bool)
(False,'a',True) :: (Bool,Char,Bool)
In general
(t1,t2,…,tn) is the type of n-tuples whose ith components have type ti for any i in 1…n.
(False,True) :: (Bool,Bool)
(False,True,False) :: (Bool,Bool,Bool)
('a',(False,'b')) :: (Char,(Bool,Char))
(True,['a','b']) :: (Bool,[Char])
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
not :: Bool -> Bool
not True = False
not False = True
even :: Int -> Bool
even x = (x `mod` 2) == 0
The argument and result types are unrestricted; Also, there can be any muber of arguments.
add :: (Int,Int) -> Int
add (x,y) = x + y
add (2, 3)
zeroto :: Int -> [Int]
zeroto n = [0..n]
zeroto 5
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!
add' :: Int -> (Int -> Int)
add' x y = x + y
-- note that the two paramaters x and y are not written in tuple-form!
add' 3 4
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
-- Another example of Curried Function:
mult :: Int -> (Int -> (Int -> Int))
mult x y z = x * y * z
mult 2 3 4
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]
incr :: Int -> Int
incr = add' 1
take5 :: [Int] -> [Int]
take5 = take 5
drop5 :: [Int] -> [Int]
drop5 = drop 5
incr 5
first5 [0..7]
drop5 [0..7]
To avoid excessive parentheses when using Curried functions, we adopt the following Currying Conventions:
->
associates to the right; e.g.Int -> Int -> Int -> Int
means
Int -> (Int -> (Int -> Int))
mult x y z
means
((mult x) y) z
Unless tupling is explicitly required, all functions in Haskell are normally defined in Curried form.
A function is called polymorphic ("of many forms") if its type contains one or more type variables. For example,
len :: [a] -> Int
len [] = 0
len (_:xs) = 1 + len xs
len [1,2,3] -- a is Int
len [False,True,True,False] -- a is Bool
len ["john","alice","bob","evan","zelle"] -- a id String
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
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"
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:
1 + 2 -- a is Int
1.0 + 2.0 -- a is Float
'a' + 'b' -- a is Char; not allowed!
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
1 + 2
1.2 + 3.4
2 == 3
'a' == 'b'
3.5 == 3.6
'a' < 'b'
2 < 3
2.5 < 1.2
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.
(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]
:type ['a','b','c']
:type ('a','b','c')
:type [(False,'0'),(True,'1')]
:type ([False,True],['0','1'])
:type [tail,init,reverse]
(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)
second xs = head (tail xs)
:type second
swap (x,y) = (y,x)
:type swap
pair x y = (x,y)
:type pair
double x = x * 2
:type double
palindrome xs = reverse xs == xs
:type palindrome
twice f x = f (f x)
:type twice