CA215 Languages and Computability - PowerPoint PPT Presentation

1 / 42
About This Presentation
Title:

CA215 Languages and Computability

Description:

Notice how patterns are used to distinguish the empty and non-empty cases ... At one stage we may produce a solution to some problem while ignoring how the ... – PowerPoint PPT presentation

Number of Views:36
Avg rating:3.0/5.0
Slides: 43
Provided by: saramor
Category:

less

Transcript and Presenter's Notes

Title: CA215 Languages and Computability


1
CA215 Languages and Computability
  • Functional Programming using Haskell

Lecture 4
2
Outline
  • Ackermann Function from Lab 3
  • Tuple Functions
  • List Functions
  • Type Checking

3
1 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
4
1 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"

5
1 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

6
1 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
7
1 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

8
1 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)?

9
1 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)?

10
2 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

11
3 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

12
3 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)?

13
3 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

14
3 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, ...

15
3 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
16
3 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

17
3 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)?

18
3 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

19
3 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

20
3 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

21
3 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

22
3 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

23
3 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)?

24
3 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...

25
3 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

26
3 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)?

27
3 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

28
3 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

29
3 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

30
3 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

31
3 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)?

32
3 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

33
3 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

34
3 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)

35
4 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

36
4 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

37
4 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

38
4 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

39
4 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)?

40
4 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

41
4 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

42
Whats in store in this weeks lab?
  • Exercises on lists
Write a Comment
User Comments (0)
About PowerShow.com