Function composition is an idea which is probably familiar from previous mathematics courses. The composition of two functions f and g is a new function h(x) = f(g(x)) also often written as f o g. It can be computed by applying f to the result of computing g. Thus if f(x) = 19x + 10 and g(x,y) = x+y then let h be the composition of f and g, so h(x,y) = 19(x + y) + 10}. This is a relatively simple example. It's important, though, to note that g is a function of two values, and thus so is h. We can see this in terms of a diagram:
The composed function takes input x, y and computes g(x,y). The result of g(x,y) is a value which becomes the input for the function f. Thus when composing functions, the domain of h is the same as the domain of g. Furthermore, the range of g must be a subset of the domain of f since f is passed values returned by g.
Now consider this simple example in its Scheme form:
(define f (lambda (x) (+ (* 19 x) 10))) (define g (lambda (x y) (+ x y) (define h (lambda (x y) (f (g x y))))
As in the mathematical example note that f is a procedure of one argument while g and h are procedures of two arguments.
We can generalize the notion of composing procedures of one argument with the following procedure:
(define compose (lambda (i j) (lambda (x) (i (j x)))))
Compose is a procedure which takes two functions as arguments and returns their composition. This demonstrates the fact that functions (or procedures) are first class objects; that is, they can be passed to a procedure (such as compose and can be returned as the result of a procedure. Thus if we have two procedures of one argument named a and b we can create c, the composition of a and b as follows.
(define c (compose a b))
Let us go back to the first example involving f and g. (Click for the definitions of f and g if you don't remember them). Suppose we also have a procedure k with the following definition.
(define k (lambda (x) (+ 4 x)))
We can define h as the composition of f and k using our new function compose:
(define h (compose f k))
The composition of f and k was fine since k was a procedure of only one argument, but our compose function is not general enough to deal with procedures of more than one argument. Think about how we could get around this. As a beginning, notice the similarity between g and k. The result of k is the same as if we always called g with 4 as its first argument. Thus we can define k in terms of g. We will call our new function g-4.
(define g-4 (lambda (y) (g 4 y)))
We have taken g, a procedure of two arguments, and used it to create a procedure g-4 of one argument. We can generalize this notion to create a procedure we call curried-g which takes one argument and returns a new procedure also of one argument. This is called a curried version of g:
(define curried-g (lambda (x) (lambda (y) (g x y)))) (define g-4 (curried-g 4)) (define g-7 (curried-g 7))
In general, currying a procedure of two arguments is creating a procedure of one argument which returns a procedure of one argument, where the composition of these procedures is the original procedure of two arguments.
Here are some additional examples of currying:
(define curried-* (lambda (x) (lambda (y) (* x y)))) (define half (curried-* 0.5)) (define double (curried-* 2)) (define quarter (curried-* 0.25)) (define quadruple (compose double double))
> (half 12) 6.0 > (double 12) 24 > (quadruple 19) 76
In class we saw the definition of curry, which accepts a procedure of two arguments and returns a curried version. We also saw the definition of uncurry, which reverses the process.
(define curry (lambda (f) (lambda (x) (lambda (y) (f x y))))) (define uncurry (lambda (f) (lambda (x y) ((f x) y))))
We can further generalize this idea of to a procedure of n arguments which we reduce to a procedure of 1 argument which returns a procedure of n-1 arguments. Repeating this reducing allows us to transform a procedure of the following form:
(lambda (a1 a2 a3 a4 ... an) ...) (lambda (a1) (lambda (a2 a3 a4 ... an) ...) (lambda (a1) (lambda (a2) (lambda (a3 a4 ... an) ...))) (lambda (a1) (lambda (a2) (lambda (a3) (lambda (a4 ... an) ...)))) (lambda (a1) (lambda (a2) (lambda (a3) (lambda (a4) ... (lambda (an) ...)))))
The result is that every procedure returned is a procedure of one argument.
Now we return to question of how to deal with composing procedures in which we don't know ahead of time the number of arguments. Suppose a is procedure of four arguments and b is a procedure of three arguments.
Scheme has a number of facilities which make dealing with multiple arguments particularly easy. Arguments can be simply thought of as lists and the Scheme primitive allows us to execute a procedure with a list as the arguments for the procedure call.
For example, mag-4 computes the magnitude of a four element list. The notion of magnitude comes from vectors in mathematics (not to be confused with Scheme vectors!) in which the magnitude of a vector is is the square-root of the sums of the squares of its components.
(define mag-4 (lambda (a1 a2 a3 a4) (sqrt (+ (* a1 a1) (* a2 a2) (* a3 a3) (* a4 a4))))) (define mag-4l (lambda (ls) (apply mag-4 ls))) > (mag-4l '(10.5 10.1 3.5 2.0)) 15.116547224812946 > (mag-4l 10.5 10.1 3.5 2.0) Error: incorrect number of arguments to #procedure mag-4l. Type (debug) to enter the debugger. > (mag-4 10.5 10.1 3.5 2.0) 15.116547224812946
This simple use of apply allows us to transform mag-4, a procedure of four arguments, into mag-4l which takes one argument, a list of four elements. Similarly, we can return multiple arguments by returning a list. This leads us to a more general multi-variable version of compose.
(define compose (lambda (f g) (lambda (ls) (apply f (apply g ls)))))
We can use a Scheme feature called unrestricted lambda or variable-arity lambda to deal with this. Procedures defined using unrestricted-lambda have the following form:
(define proc (lambda lst ... body ...))
Note the intentional lack of parentheses around lst. When applied to any number of arguments, proc will have access to all the actual arguments as a single list which it refers to as "lst". The individual arguments in the list lst can be accessed using the usual list operations car, cadr, cdr, and so on.
For example, here is a silly function that asks to be fed if given no arguments, tells you if it is a number in the case that it's given just one argument, and applies the first argument to the rest in case it's fed many arguments. :
(define silly (lambda args (cond ((null? args) 'feed-me) ((null? (cdr args)) (number? (car args))) (#t (apply (car args) (cdr args)))))) > (silly 24) #t > (silly +) #f > (silly + 1 2 3 4 5) 15 > (silly) feed-me >
As you have probably noticed by now, Scheme's +,*, and - procedures are designed using unrestricted lambda so that:
> (+ 10 12 9 3) 34 > (- 6 -3 2 8) -1 >
Here is compose once again. This version deals properly with procedures of more than one argument:
(define compose (lambda (f g) (lambda ls (apply f (apply g ls)))))
There is still an unpleasantness and that is that since we are using
"apply" with f we must not allow g to return an atomic value -- it must
return a list.
((compose 1+ +) 2 3)
(define fac (lambda (n) (cond ((zero? n) '(1)) (else (list (apply * (cons n (fac (- n 1))))))))) (define plus (lambda args (list (apply + args)))) > ((multi-compose fac plus) 1 2 3) (720) > ((multi-compose) 1 2 3) (1 2 3) > ((multi-compose fac) 5) (120) > ((multi-compose plus) 1 2 3 4 5) (15) > ((multi-compose plus plus fac fac plus) 2 2) (620448401733239439360000) >