Title: CA215 Languages and Computability
1CA215 Languages and Computability
- Functional Programming using Haskell
Lecture 4
2Outline
- Ackermann Function from Lab 3
- Tuple Functions
- List Functions
- Type Checking
31 Ackermann Function Problem
- In computability theory, the Ackermann function
is defined for non-negative integers x and y as
follows -
- Construct a Haskell function "ackermann" which
takes two Integers (x and y) as input and outputs
its Ackermann value (another Integer). You should
use patterns to implement each of the three
definitions.
y 1 if x 0 A(x,y) A(x - 1,
1) if x gt 0 and y 0 A(x - 1, A(x, y -
1)) if x gt 0 and y gt 0
41 Ackermann Function
y 1 if x 0 A(x,y) A(x - 1,
1) if x gt 0 and y 0 A(x - 1, A(x, y -
1)) if x gt 0 and y gt 0
- Hint To handle mgt0 condition, remember the
following rule governing nk patterns "The
pattern nk only matches a value m if m gt k"So
mgt0 implies m gt 1 which implies the nk pattern
n1"
51 Ackermann Solution
- Construct a Haskell function "ackermann" which
takes two Integers (x and y) as input and outputs
its Ackermann value (another Integer). - Type declaration
- ackermann Int -gt Int -gt Int
61 Ackermann Solution
- You should use patterns to implement each of the
three definitions. Whats the first thing we
define when using a pattern definition? - The base case
- Whats the base case here?
y 1 if x 0 A(x,y) A(x - 1,
1) if x gt 0 and y 0 A(x - 1, A(x, y -
1)) if x gt 0 and y gt 0
71 Ackermann Solution
- How do we define that first line
- (A(x,y)y 1, if x 0.) in terms of
patterns? - ackermann 0 y y 1
- What about the next case
- (A(x,y)A(x - 1,1), if x gt 0 and y 0.) ?
- ackermann (x1) 0 ackermann x 1
81 Ackermann Solution
- And the final case (A( x,y )
- A(x - 1,A(x, y - 1)), if x gt 0 and y gt 0) ?
- ackermann (x1) (y1)
- ackermann x (ackermann (x1) y)?
91 Ackermann Solution
y 1 if x 0 A(x,y) A(x - 1,
1) if x gt 0 and y 0 A(x - 1, A(x, y -
1)) if x gt 0 and y gt 0
- ackermann 0 y y 1
- ackermann (x1) 0 ackermann x 1
- ackermann (x1) (y1)
- ackermann x (ackermann (x1) y)?
102 Functions on Tuples
- We usually use patterns to define functions on
tuples - For example, here is a function to add a pair of
integers - addPair (Int, Int) -gt Int
- addPair (x, y) x y
- The pattern (x, y) matches any pair and sets x to
be the first element of the pair and y to be the
second element - Example (0.0, a) matches any pair where the
first element is the Float value 0.0 the second
element of the pair is bound to a - Example (0, (5, answer)) matches any pair
whose first element is 0 and whose second element
is a pair whose first element is the character
5 the second element of the second element is
bound to answer
113 Review of Lists
- A list is a sequence (or collection) of elements
of the same type, eg. 1,2,3 is of type Int,
"Mary","John","Sara" is of type String, etc. - Every list is either empty, and it is then
represented by , or non-empty, and then it can
be written in the form xxs where - x is the first item of the list and it is called
head - xs is the remainder of the list and it is called
tail - For example 1,2,3 can be represented as
12,3, where 1 is the head and 2,3 is the tail
123 Review of Lists
- Every list can be built up repeatedly by applying
the infix operator (cons)? - is a constructor for lists that makes a list by
putting an element in front of an existing list - 123
- 123
- 12,3
- 1,2,3
- Note that here we use the fact that is right
associative, so that for any values of x, y and
zs - xyzs x(yzs)?
133 Review of Lists
- Every list is either empty or has the form xxs,
where x is called the head of the list and xs is
called the tail. - The built-in functions head and tail can be used
to extract the head and tail respectively from a
list. For example - head 1,2,3,4 1
- tail 1,2,3,4 2,3,4
- The infix operator (append) joins two lists
together - 1,2,3 4,5,6 1,2,3,4,5,6
- The infix operator !! selects the nth element of
a list - 1,9,37,8 !! 2 37
143 Review of Lists
- Haskell provides a special ellipsis or
dot-dot syntax for arithmetic progressions. For
example - 1..5 1,2,3,4,5
- To specify step sizes other than 1, give the
first 2 numbers. For example - 1, 3..10 1,3,5,7,9
- 0,10..50 0,10,20,30,40,50
- We can also have infinite lists. For example
- 1.. 1,2,3,4,5,6, ...
- 1,3.. 1,3,5,7,9, ...
153 List Functions
- Suppose we wanted to define a function to add
together all elements of a list of integers
(there is a standard function sum that does this,
but ignore this for the sake of the exercise)? - sumList Int -gt Int
- For a fixed length it is easy
- sumList3 x, y, z x y z
- But how about a function sumList that would work
for any length?
List declaration
163 List Functions
- Just as we described calculating factorials, we
can think of laying out the values of sumList as
follows - sumList 0
- sumList 8 8
- sumList 37,8 45
- sumList 9,37,8 54
- sumList 1,9,37,8 55
- And just as in the case of factorial, we can
describe the whole process by describing the
first line and how to go from one line to the
next as follows - the sum of the empty list is 0
- the sum of a non-empty list xxs is x sum of xs
173 List Functions
- The inductive design of sumList translates
directly into the following Haskell definition
(using patterns) - sumList Int -gt Int
- sumList 0
- sumList (xxs) x sumList xs
- This gives a definition of sumList by primitive
recursion over lists - In such a definition we give
- a starting point the value of sumList at ,
- a way of going from the value of sumList at a
particular point sumList xs to the value of
sumList at the next point, namely sumList (xxs)?
183 List Functions
- sumList Int -gt Int
- sumList 0
- sumList (xxs) x sumList xs
- Notice how patterns are used to distinguish the
empty and non-empty cases - Notice how the second clause defines sumList in
terms of sumList applied to a smaller argument - The argument in the recursive calls gets smaller
and smaller until it is the empty list and hence
the recursion terminates through the first
clause, ie. the base case - Notice how the definition is like mathematical
induction the base case is a list of length 0
and the inductive case defines the function for
lists of length n1 in terms of the result for
lists of length n
193 List Functions
- Consider the evaluation of sumList 1,9,37,8
- Using the equation sumList (xxs) x sumList
xs repeatedly we have - sumList 1,9,37,8
- ? 1 sumList 9,37,8
- ? 1 (9 sumList 37,8)?
- ? 1 (9 (37 sumList 8))?
- ? 1 (9 (37 (8 sumList )))?
- And then using the equation sumList 0 and
integer arithmetic we get - ? 1 (9 (37 (8 0)))?
- ? 55
- The recursion used to define sumList will give an
answer on any finite list since each recursion
steps takes us closer to the base case where
sumList is applied to
203 List Functions
- Example length is a standard function but how
would we define it ourselves? - Design
- base case the length of is 0
- inductive case a non-empty list is 1 element
longer than its tail - length a -gt Int
- length 0
- length (xxs) 1 length xs
- Try it yourself evaluate the following
expression length 1,9,37,8
213 List Functions
- Example a function to double every element of a
list of integers - double 1,9,37,8
- ? 2,18,74,16
- Design
- base case doubling all elements of returns
- inductive case multiply the head of the list by
2 and use (cons) to put it in the front of
double of the tail of the list - double Int -gt Int
- double
- double (xxs) (2 x) double xs
- Try it yourself evaluate the following
expression double 1,9,37
223 List Functions
- Example how would we define the (standard) list
append function () that joins two lists
together? - 2,3,4 9,8
- ? 2,3,4,9,8
- This is trickier to design because () takes 2
lists - Should we try induction on the first list, the
second list or both? - For recursion on both lists we have the following
4 cases, the combination of base and inductive
cases for each list - ...
- (yys) ...
- (xxs) ...
- (xxs) (yys) ...
- If we start with this collection, we may find
some redundancies and eliminate them
233 List Functions
- Examples taking 2 for x and 3,4 for xs we have
- 2,3,4 9,8 ? 2,3,4,9,8
- 3,4 9,8 ? 3,4,9,8
- So we get 2,3,4 9,8 by putting 2 on the
front of 3,4 9,8 - In the case that the first list is empty
- 9,8 ? 9,8
- We finally get the following definition for ()
- () a -gt a -gt a
- ys ys
- (xxs) ys x(xsys)?
243 List Functions
- So in fact, recursion on just the first list is
sufficent to define this particular function - The first two cases do not need to be separated
- ...
- (yys) ...
- are covered by a single clause ys ys
- The third and fourth cases
- (xxs) ...
- (xxs) (yys) ...
- are also combined into (xxs) ys x(xsys)?
- Attempting to define () by induction on only
the second list does not seem possible - xs xs
- xs (yys) ?
- The problem is that the basic list constructor
adds elements to the front of a list the pattern
(yys) binds y to some element that will end up
in the middle of the result list...
253 List Functions
- Design a function that sorts a list of integers
into ascending order - iSort Int -gt Int
- For example iSort 7,3,9,2) 2,3,7,9
- As in all previous examples, our first intuition
should be the two cases - iSort ...
- iSort (xxs) ...
- The first case is easy since the empty list is
trivially sorted - iSort
263 List Functions
- For non-empty lists we have direct access to the
head x and the tail xs, ie. 73,9,2 - Typically, there is a recursive call of the
function on the tail of the list - Hence we might expect the second clause to look
like - iSort (xxs) ...(iSort xs)...
- For the example above, we have x equal to 7 and
iSort xs - equal to 2,3,9
- To build the final result, iSort (xxs) from x
and iSort xs, we need to insert 7 into the
correct position in 2,3,9 - Hence we can write
- iSort (xxs) insert x (iSort xs)?
273 List Functions Top-down design
- This is a very easy example of top-down design
- iSort has been defined assuming that we can
define insert - Top-down design involves breaking a problem into
smaller and smaller sub-problems until they
become manageable or trivial - How we go about this process of factoring into
smaller parts is the essence of program design
283 List Functions and Abstraction
- The most basic skill in designing and
constructing programmes in any language is
abstraction - All we need to know to understand and design
iSort is what the insert function does - The details of how insert works is irrelevant to
the definition above - At one stage we may produce a solution to some
problem while ignoring how the subcomponents
work we are only interested in what they do - Then we may concentrate separately on how each
subcomponent is to work, while ignoring why the
overall design needs them - Having factored the overall problem into smaller
sub-problems, we must assess whether the
decomposition gave us simpler sub-components if
not, we will re-visit the overall design - Programme design and development is an iterative
process
293 List Functions
- We have written iSort, using a function insert
which we must now define - Again our intuition should be to try the standard
inductive approach - insert Int -gt Int -gt Int
- insert x ...
- insert x (yys) ...
- To insert x into an empty list maintaining
ascending ordering is trivial - insert x x
- If the list is non-empty, we have direct access
to its head we may ask does x belong before or
after the head of the list? - if x belongs before the head, simply cons () it
there and we are done - if x belongs after the head, we can use a
tail-recursive call of insert to find out where
it belongs - insert x (yys)?
- x lt y xyys
- otherwise yinsert x ys
303 List Functions
- Thus the definition of iSort gives
- iSort Int -gt Int
- iSort
- iSort (xxs) insert x (iSort xs)?
- insert Int -gt Int -gt Int
- insert x x
- insert x (yys)?
- x lt y xyys
- otherwise yinsert x ys
- Try it yourself evaluate iSort 3,9,2
313 List Functions
- Pattern matching is a readable and powerful way
of defining list functions in Haskell - The pattern restricts the set of values to which
an equation applies - Patterns are not arbitrary expressions but
literal values (such as 0, , etc.) or
constructed values (such as (n1), (xxs), etc.)
and hence sqrt (x2) x is illegal - the pattern matches an empty list
- the pattern (xxs) matches a non-empty list,
binding x to the head of the list and xs to the
tail of the list - Variables cannot be repeated on the left-hand
side of a definition - findReps (xxxs) xfindReps xs is illegal
- and so is
- find code ((code,name,price)rest) (name,price)?
323 List Functions
- Define a function that sums the pairs of elements
in a list - sumPairs (Int,Int) -gt Int
- sumPairs (1,9),(37,8)
- ? 10,45
- The base case is standard sumPairs
- We can choose a variety of patterns for the
inductive case - Alternatives
- sumPairs1 (pps) (fst p snd p)sumPairs1 ps
- sumPairs2 (pps) (mn)sumPairs2 ps
- where (m,n) p
- sumPairs ((m,n)ps) (mn)sumPairs ps
- In the final alternative, the most precise
pattern has been used, leading to the simplest
and clearest definition of the function
333 List Functions
- Haskell attempts to match patterns in a strictly
sequential order left to right in a definition
and top to bottom in a sequence of definition
clauses - Note that as soon as a pattern matches fails,
that equation is rejected and the next one tried - Consider
- myZip a -gt b -gt (a,b)
- myZip (xxs) (yys) (x,y)myZip xs ys
- myZip xs ys
- If the first argument is then the match on
(xxs) fails and that clause is rejected without
attempting to match the second argument myZip
truncates the longer of the two list arguments
343 Review of List Comprehensions
- List comprehensions are a convenient way to
define lists where the elements must all satisfy
certain properties. - For example, the squares of all the values from 1
to 10 which are even is defined as follows - x2 x lt- 1..10, even x
- x lt- 1..10 is the generator and even x is the
filter - With two generators, we get all possible pairs of
values - (x,y) x lt- 1..3, y lt- a..b
- (1,a),(1,b),(2,a),(2,b),(3,a),(3,b)
354 Type checking
- Strongly typed languages such as Java give
programmers security by restricting their freedom
to make mistakes - Weakly typed languages such as Prolog allow
programmers more freedom and flexibility with a
reduced amount of security - The Haskell type system gives the security of
strong type checking as well as flexibility - Every expression must have a valid type, but it
is not necessary for the programmer to include
type information this is inferred automatically
364 Type Checking
- Given little or no explicit type information,
Haskell can infer all the types associated with a
function definition - For example
- the types of constants can be inferred
automatically - identifiers must have the same type everywhere
within an expression - Other type checking rules are applied for each
form of expression - For example, consider definitions with guards
- function pattern
- guard1 e1
- guard2 e2
- ...
- guardn en
- The following type checking rules apply
- each of the guards gi must be of type Bool
- the values ei must have the same type
374 Type Checking
- Consider the following function
- isSpace c c
- The constant is a character
- Because c is compared to a Char, it must also be
a Char - The argument to isSpace must therefore be a Char
- The body of isSpace is a comparison, so it must
be of type Bool - The return type of isSpace is therefore a Bool
- isSpace is therefore of type Char -gt Bool
- We can use Hugs to query the type of this
function as follows - type isSpace
- Hugs will respond as follows
- isSpace Char -gt Bool
384 Type Checking
- Type inference leaves some types completely
unconstrained in such cases the definition is
polymorphic (this is parametric polymorphism)? - For example, consider the function reverse for
reversing a list - The following type is inferred for this function,
meaning reverse has a set of types - a -gt a
- When we apply reverse, Haskell works out which
type reverse is being used at - reverse 1,2,3 the function is applied to a
list of Int and so it is used at type Int -gt
Int - reverse a,b,c the function is applied to
a list of Char and so it is used at type Char
-gt Char
394 Type Checking
- In the previous example a -gt a, a is a type
variable - Types with variables (eg. a, b) are called
polytypes - Types without type variables are called monotypes
(eg. Int, Bool)? - A polytype can be thought of as standing for a
collection of monotypes which can be obtained by
instantiating the type variable with monotypes
(eg. Int, Bool)?
404 Type Checking
- Hugs can work out the type of any expression and
any function from its definition - Even if you declare the type of a function, Hugs
will work it out anyway and check whether you are
right - You should always declare the type of any
functions you are defining in your programmes - the type of a function is a basic part of its
design how can you be clear about a functions
behaviour unless you at least know the types of
its arguments and the type of its result? - type declarations are an important part of
programme documentation - type declarations help you to find errors
- If Hugs figures out that the type of a function
is different to what you expect, then you have
made an error
414 Type Checking
- For example, suppose you wish to design a
function myEven such that myEven x return True if
x is divisible by 2 and False otherwise (there is
already a standard function even, hence the
name)? - How about
- myEven Int -gt Bool
- myEven x x div2
- When loading a script containing this definition,
Hugs will give an error message - Type checking
- ERROR . . . Type error in function binding
- term myEven
- term Int -gt Int
- does not match Int -gt Bool
- Hugs is indicating that it can tell from the
definition that myEven has a type that is
different from its declaration - If we had not declared its type, a less obvious
error message would have been sent when we used
myEven
42Whats in store in this weeks lab?