One kind of flexibility that Scheme programmers get from having procedures
as values is the ability to fill in different arguments of a multi-argument
procedure at different points in the computation. For example, consider
the following substitute
procedure, which takes three
arguments -- template
, which should be a list, and
old
and new
, which might be values of any type --
and returns a list just like template
except that
new
has been substituted for every element of
template
that is equal to old
(as determined by
the equal?
predicate):
(define substitute (lambda (template old new) ;; precondition test (if (not (list? template)) (error 'substitute "The template must be a list")) (let kernel ((rest template) (result '())) (if (null? rest) (reverse result) ;; Reverse the final list, because the ;; recursion builds it back to front. (let ((first (car rest))) (kernel (cdr rest) (cons (if (equal? old first) new first) result)))))))
In many cases where this procedure might be applied, the values of
old
and new
are obtained before the value of
template
is even considered. One might, therefore, want to
write a procedure that takes just old
and new
as
arguments and returns a procedure that waits for the template:
(define sub (lambda (old new) (lambda (template) (substitute template old new))))
Now a definition like
(define year-replacer (sub 'year 1997))
makes year-replacer
a substitution procedure that performs one
specific substitution on any template:
> (year-replacer '(ear here year sheer year year beer tier here)) (ear here 1997 sheer 1997 1997 beer tier here) > (year-replacer '(year month day)) (1997 month day) > (year-replacer '(no replacement here)) (no replacement here)
Using sub
, define and test a month-replacer
procedure that substitutes the symbol November
for each
top-level occurrence of the symbol month
in a given list.
A procedure that is derived from another procedure by filling in some but not all of its arguments is called an ``operator section.'' You don't need the ``procedures as values'' idea to create individual operator sections, if you know the values that you want to fill in when you're writing the program. A procedure as simple as
(define double (lambda (n) (* n 2)))
qualifies as an operator section, since it fills in the second argument to
the *
procedure with a particular value. The extra power that
you get in Scheme is the ability to generalize the process of
constructing operator sections, as in the sub
procedure, which
actually builds and returns a new operator section during the execution of
the program. The programmer may not even know what will be substituted for
what at run time (the values of the parameters old
and
new
might, for instance, be read in from a file that is
prepared long after the program is written and compiled); she can
nevertheless direct the construction and use of an appropriate operator
section without revising the program in any way.
To curry a procedure that has two or more arguments is to rewrite it, repeatedly using this mechanism for operator sectioning, so that each of its arguments is supplied separately:
(define curried-substitute (lambda (template) (lambda (old) (lambda (new) (substitute template old new))))) > (((curried-substitute '(a b c b d b e)) 'b) 'f) (a f c f d f e)
In other words: Applying the curried-substitute
procedure to
the list (a b c b d b e)
yields a procedure which, when
applied to the symbol b
, yields another procedure which, when
applied to the symbol f
, finally returns the result list
(a f c f d f e)
. Either of the intermediate procedures could
easily be split out and given a name:
(define new-for-old (curried-substitute '(a b c b d b e))) > ((new-for-old 'b) 'f) (a f c f d f e) (define new-for-b (new-for-old 'b)) > (new-for-b 'f) (a f c f d f e)
One way of looking at this is to think of the intermediate procedures as
being used for data storage: new-for-old
is ``remembering''
the value of the filled-in parameter template
, and
new-for-b
is remembering both the value of
template
and the value of old
, so that the only
parameter that remains to be supplied in the last call is new
.
Write a curried version of the expt
procedure.
Curried-expt
should take one argument, a number
x
, and return a procedure that ``remembers'' x
and raises it to any specified power:
(define power-of-two (curried-expt 2)) > (power-of-two 7) 128 > ((curried-expt 10) 3) 1000 > ((curried-expt -2) 5) -32 > ((curried-expt 9/10) 4) 6561/10000 > (map (curried-expt 9) '(2 3 1/2 -3)) (81 729 3.0 1/729)
This process or writing a procedure that returns a procedure is called currying -- named after the logician Haskell B. Curry.
The insertion-sort lab showed how a procedure could be defined that returns a list of numbers in ascending order. In that lab, an ordering predicate (e.g., <= or =>) is used to compare specific data, but all of the rest of the code is independent of the type of data and the nature of the ordering required.
The same idea of currying can be applied to produce a proceduresort-shell
that takes an ordering predicate (e.g., <= or =>)
as parameter and that returns a sorting procedure based on that predicate.
Thus, an alternative definition of sort-ascending
might be:
(define sort-ascending (sort-shell <=))
while a procedure for sorting list elements in descending order might be:
(define sort-descending (sort-shell >=))
sort-shell
.
A compose
procedure may be defined that takes any two
procedures f
and g
of arity 1 as arguments and
returns a single procedure that is a composite of the two, in the sense
that the value it returns can be obtained by applying g
to its
argument and then f
to g
's result.
(define compose (lambda (f g) (lambda (x) (f (g x)))))
The call (compose car reverse)
returns a procedure. Describe
the effect of applying this procedure to a list.
Suppose that we have two procedures f
and g
of arity 1 that always return numbers as values. We can perform ``function
addition'' on them -- that is, we can use them to generate a new procedure
that takes one argument and returns the sum of the results of applying
f
and g
to that argument. Define a procedure
function-add
that implements the operation of function
addition.
> ((function-add double /) 5) 51/5 > ((function-add (lambda (n) (* n n)) (lambda (n) (- 128 n))) 100) 10028 > ((function-add string-length (lambda (str) (char->integer (string-ref str 0)))) "America") 72 ;; 7 characters in "America", #\A is ASCII character 65 (define sin-plus-cos (function-add sin cos)) > (sin-plus-cos 0) 1 (define pi 3.1415926535897932) > (sin-plus-cos (/ pi 4)) 1.414213562373095 (define sum (lambda (ls) (apply + ls))) > ((function-add length sum) '(3 2 4 9)) 22
Why do we need to define a separate procedure function-add
,
instead of simply applying the built-in addition procedure, +
?
In the last example above, for instance, would there be anything wrong with
simply writing ((+ length sum) '(3 2 4 9))
? If so, what?
This document is available on the World Wide Web as
http://www.walker.cs.grinnell.edu/courses/153.sp00/lab-higher-order-proc.html
created April 2, 1997 by John David Stone
last revised February 29, 2000 by Henry M. Walker