Composing and Currying Functions

Chapter: Composing and Currying Functions

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) = 0.9x + 10 and g(x,y) = x+y then let h be the composition of f and g, so h(x,y) = 0.9(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.

Q. 1
What will be the range of h (the composition of f and g)? Consider that f may not be passed all the values in its domain since the range of g could be a proper subset of the domain of f.


Now consider this simple example in its Scheme form:

(define f
  (lambda (x)
    (+ (* 0.9 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.

Q. 2
Suppose we have two arbitrary procedures a and b both of one argument. Define a procedure c which is the composition of a and b.


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 that 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 (again such as compose. 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))

Q. 3
Without running it, what should be the result of (h 6)?


Q. 4
What if we want to compose f and g? As before, (define h (compose f g)). Now what is the result of (h 6)?


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

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.


Exercise 1

Write a function curry-maker which takes a procedure of two arguments and returns a curried form of that procedure. If foo is a function of two arguments (lambda (x y)) then (curry-maker foo) will be a function of one argument that (when applied) returns a function of one argument.

curry-maker should perform as follows:

 
> (define cons-curried (curry-maker cons))
> (define cons-a (cons-curried 'a))
> (cons-a '(big dog))
(a big dog)
> ((cons-curried 'b) (cons-a '(big dog)))
(b a big dog)
> (define add-curried (curry-maker +))
> (define add2 (add-curried 2))
> (add2 3)
5
> ((add-curried 42) 23)
65



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.

Q. 5
What will be some problems if we try to do (compose a b) using compose as defined?


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 apply 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)
procedure mag-4l: expects 1 argument, given 4: 10.5 10.1 3.5 2.0
> (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)))))

Q. 6
This works well except for one annoyance. Try some examples. What is the problem?


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)))
      (else (apply (car args) (cdr args))))))

> (silly 24)
#t
> (silly +)
#f
> (silly + 1 2 3 4 5)
15
> (silly)
feed-me
>

For completeness, we remark that we can also deal explicitly with a more restrictive case in which we have one required argument followed by some number of optional arguments. We use the notation of improper lists or dotted-pair notation. If you are interested, why not play around with an expression like (lambda (x y . more) ...).

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.

Q. 7
What happens if you type
((compose 1+ +) 2 3)


Q. 8

Is anything else bothering us?


I'm going to use the term "inputs a bunch of numbers" to refer to functions like plus that are applied like (plus 1 2) or (plus 1 2 3 4). I would use the term "inputs a list of numbers" to refer to a function such as foo that takes arguments like (foo '(1 2)) or (foo '(1 2 3 4)). Similarly, I use the term "returns a list of numbers" for plus and would say that + "returns a number". Let us focus our attention on functions, like plus, designed to input a bunch of numbers and output a list of numbers.


Exercise 2




rhyspj@gwu.edu