Course 287 Lecture 5: Functions on Lists, Higher Order Functions


The append  function joins two lists together to make one.
The member?   function asks whether an object belongs to a list
The expression (member x list)   finds an object x in the list.
The construct (and expr-1....expr-n) is a special form.
The construct (or expr-1...expr-n) is a special form.
The cond  special-form is more convenient than nested if's
The select_set  function lets you choose members out of a list
The trace  function allows you to see what a program is doing.
The example  function allows you run tests on functions as you compile
The list  function is more convenient than cons  for building longer lists.
lambda   expressions which take a variable number of arguments
Some examples of the use of map_list
The map  function is built-in, resembles map_list .
The reduce  function further generalises map_list .

Abelson and Sussman

The discussion in Abelson and Sussman section 2.2, up to the end of 2.2.1 is parallel to this lecture (p97 ff.). You will find a discussion of Procedures as Arguments in section 1.3.1 of Abelson and Sussman (pp57 ff).

In this lecture we learn about the special forms cond , trace . We learn also about the useful built-in functions append , member map , example  and list . We also write our own version of member, member?  . These built-in functions will help us implement algorithms on finite sets.

The append  function joins two lists together to make one.

The append  function is built into Scheme. It concatenates two lists, that is to say, given two lists list1  and list2  it produces a new list which starts with the same elements as list1  and finishes with those of list2 .


(define (append list1 list2)
        (if (null? list1) list2
            (cons (car list1) (append (cdr list1) list2))))
(append '(1 2 3) '(4 5 6))

    ==>  (1 2 3 4 5 6)

It is essential that you distinguish between cons  and append . The append  function always works on lists, and combines the elements of the lists at the same level. A call (cons x y) of the cons  function makes x be the first element of the new list:

(cons '(1 2 3) '(4 5 6))

    ==>  ((1 2 3) 4 5 6)

If we use more complicated lists we see that:

(append '((1 2) 3) '(4 (5 6)))


    ==>   ((1 2) 3 4 (5 6))

here the first argument of append  is a list whose members are, in sequence, the list '(1 2) and the number 3, while the second argument of append  is the list whose members are, in sequence, the number 4 and the list '(5 6).

The member?   function asks whether an object belongs to a list

The member?   function determines whether an element is a member of a list. Again, we proceed by recursion. If the list  is empty (1), then (member? list) is false. Otherwise, if list  begins with something equal to x then clearly (member? list) is #t. Otherwise we ask if x is a member of the tail of the list.


(define (member? x list)
     (if (null? list) #f                                ;(1)
         (if (equal? x (car list)) #t                   ;(2)
              (member? x (cdr list)))))                 ;(3)

The member   function

Note that there is a function member built into Scheme. (member x list) evaluates to #f if x is not a member of list, otherwise it evaluates to the that part of list which begins with the first occurrence of x.

    (member 2 '(4 5 2 6 7)) ==> '(2 6 7)
    (member 2 '(6 7 8)) ==> #f

The special forms and and or

The boolean functions and and or are implemented in Scheme as special forms:
        (and expr-1 ... expr-n)
        (or  expr-1 ... expr-n)

Evaluation of the arguments in each case starts from the left, and as soon as the value of the special form is manifest, evaluation stops. Note that and and or can only be used in this form. For example and is not a legal Scheme expression.

The cond  construct is more convenient than nested if's

Nested if statements like the one above are tedious to write. Instead the experienced programmer can use the cond  construct:

     (cond clause1 clause2 ... clausen)

Here each clausei has the form

(testi expressioni1 ... expressionimi)

except that the last clause may have the form

(else expressionn1 .... expressionnmn)

The meaning of the cond  construct is that the testi of each clausei is evaluated in turn, and in the first clausei for which the testi succeeds the expressioni1 ... expressionimi are evaluated, the value of the whole cond  expression being the value of the last expression evaluated in the chosen clause.

An example of the use of cond  is the following:


(define (member? x list)
     (cond ((null? list) #f)
           ((equal? x (car list)) #t)
           (else   (member? x (cdr list)))))

(member? 2 '(4 2 3))
#t

(member? 2 '(the fat cat))
#f

Warning

It is best to avoid the use of cond  until you are familiar with Scheme, since there are various possibilities of confusion. Clauses in cond  expressions are not evaluated as though they were normal expressions, since the first member is not applied to the rest as arguments. Also the fact that one can have a sequence of expressions in a clause can cause confusion because the value of the cond  expression is the value of the last member of the sequence. All the other values are thrown away.

Within the functional paradigm there is no point in evaluating expressions that are not incorporated in the value of a function, so the only possible reason for having more than two expressions in a clause might be to print out some monitoring information for program development. For example:


(define (member? x list)
     (cond ((null? list) #f)
           ((equal? x (car list)) (display "found it") #t)
           (else   (member? x (cdr list)))))

The use of the tracer and debugger built into UMASS Scheme should mostly obviate the need to insert such extra expressions.

(case expr cases) chooses a case depending on the value of expr

The case expression has the form (case expr cases) where
    cases ->  case cases
    cases ->  case 
    case -> ((constants) expr)
    case -> (else expr)
    constants -> constant constants
    constants -> constant
To evaluate a case statement, Scheme evaluates the expression expr and then examines each case until it finds the first in which the value of the expression occurs among the constants of the case. The expression of the case is then evaluated, and is the value of the case expression. If no such constant is found the value of the case expression is undefined. If an (else... case is found, then its expression is evaluated, and is the value of the case statement.

(example
    '(case (* 2 3)
       ((2 3 5 7) 'prime)
       ((1 4 6 8 9) 'composite))
   'composite)



(example
    '(case 3
       ((2 3 5 7) 'prime)
       ((1 4 6 8 9) 'composite))
    'prime)



(example
    '(case (car '(c d))
       ((a) 'a)
       ((b) 'b))
    (display 'ok))


(example
    '(case (car '(c d))
       ((a e i o u) 'vowel)
       ((w y) 'semivowel)
       (else 'consonant))
        'consonant)

(example
    '(case 'a
       ((a e i o u) 'vowel)
       ((w y) 'semivowel)
       (else 'consonant))
        'vowel)


(example
    '(case 'w
       ((a e i o u) 'vowel)
       ((w y) 'semivowel)
       (else 'consonant))
        'semivowel)

The select_set  function allows us to choose members of a list.

The expression (select_set p l) evaluates to a list of those members of the list l which satisfy the predicate p


    (define (select_set p l)
        ;selects those members of l which satisfy p
        (cond
            ((null? l) '())
            ((p (car l)) (cons (car l)(select_set p (cdr l))))
            (else (select_set p (cdr l)))
            )
        )


    (example '(select_set (lambda (x) (< 5 x)) '(2 44 5 3 99)) '(44 99))

Remark Could we write the expression evaluated in the example  above more succinctly as:

    (select_set  (<  5) '(2 44 5 3 99))

Unfortunately, in Scheme, the answer is "no", although this kind of construct is perfectly legal in the lambda calculus. Some functional languages with good support of the functional paradigm, for example POP-11 and especially Haskell, provide this kind of construct, called, in POP-11, "partial application".

The trace  function allows you to see what a program is doing.

You can see what your programs are doing using the trace  function. If we make the definition:


(define (member? x list)
     (cond ((null? list) #f)
           ((equal? x (car list)) #t)
           (else   (member? x (cdr list)))))

and then say to Scheme


    (trace member?)

we now find that evaluating:


    (member? 2 '(4 2 3))

causes the following trace of the evaluation of the member?   function to appear.

(member?  2 (4 2 3) )
|(member?  2 (2 3)
|member?   = #t
member?   = #t

The example  function allows you run tests on functions as you compile

UMASS Scheme contains a built-in example  function, which allows you to put examples of what your functions are supposed to do into your code. These examples will be run and checked.

If you put the line


    (example '(member? 2 '(4 2 3)) #t)

The print-out will be:

    example: (member? 2 '(4 2 3)) = #t,  ok!

On the other hand, if you put in:


    (example '(member? 2 '(4 2 3)) #f)

You will get an error report.

    example: (member? 2 '(4 2 3)) = #f
                 example failed, evaluating: (member? 2 '(4 2 3))
                 value returned: #t
                 value expected: #f

The list  function is more convenient than cons  for building longer lists.

It is rather a pain to make a list of several elements using cons . So Scheme provides us with an easier construct.

(list expr1 ..... exprn)

This evaluates to a list of the values of expr1 ... exprn For example,

(list `the (+ 4 5) `cats)

evaluates to

(the 9 cats)

You can pass the whole list of arguments into a lambda   expr.

A permissable form for lambda   expressions is

    (lambda variable body)

that is, there are no parentheses around the single argument. When such a lambda expression is applied to arguments, Scheme makes a list of the arguments and binds that list to the variable.


    (define fred (lambda x (append x x)))

(fred 1 2 3) will produce '(1 2 3 1 2 3). The list function built into Scheme can be defined as:


    (define list (lambda x x))

Some examples of the use of map_list 

Recall that in the previous lecture we defined the function map_list  as a general kind of way of making a systematic transformation of a list into one in which elements of the first list were replaced by elements transformed by a function. Below are some examples of the use of map_list .


(define (succ x) (+ 1 x))

(example
    '(map_list succ '(3 4 5))
    '(4 5 6)
    )


(example
    '(map_list (lambda (x) (+ x 5)) '(7 8 9))
    '(12 13 14))


(example
    '(map_list (lambda (x) (> x 5)) '(2 9 5))
    '(#f #t #f)
    )


(example
    '(map_list (lambda (x) (cons  x '())))
    '((a) (b) (c))
    )


(example
    '(map_list (lambda (x) (if (> x 10) x 0)) '(2 20 5 17))
    '(0 20 0 17)
    )


(example
    '(map_list car
    '((a b) (c d) (e f)))
    )

The map  function is built-in, resembles map_list .

There is a built-in function map  which, when used with two arguments, is identical with map_list . In general the expression
    (map  f  e1 ... en)
will evaluate correctly if f is a function of n arguments, and all the ei are lists of the same length. In that case the result is a list each of whose members is obtained by applying f successively to the members of the lists ei.

For example

    (example
        '(map  + '(2 3 4) '(5 6 7))
        '(7 9 11))

The reduce  function further generalises map_list .

Recall we defined the map_list  function by generalising a function that built a new list out of the square-roots of an existing list.


(define (map_list f l)
   (if (null? l)
      '()
       (cons (f (car l)) (map_list f (cdr l)))))

This function leaves us further scope for generalising operations over a list that are conducted element-by-element. Essentially, instead of building a new list as answer, we could arrange to create any new answer, for example a number. All we need to do is to replace the list-creating parts of map_list  by parameters. We replace the empty list by a parameter called base and the list-building function cons  by a parameter called acc (for accumulate).


(define (reduce f acc base l)
   (if (null? l)
       base
       (acc (f (car l)) (reduce f acc base (cdr l)))))

For example, if we use 0 for the base and + for the accumulator, we will be adding up numbers mapped by f. Thus


    (example '(reduce (lambda (x) x) + 0 '(1 2 3)) 6)

(define (or1 x y) (or x y))

(define (member? x l)
    (reduce (lambda (y) (equal? x y)) or1 #f l))

(member? 3 '(3 4 5))
(member? 44 '(3 4 5))
(trace reduce)
(trace or1)