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 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 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)
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
(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.
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
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.
cases -> case cases cases -> case case -> ((constants) expr) case -> (else expr) constants -> constant constants constants -> constantTo 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 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".
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
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
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)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))
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)))
)
(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))
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)