schemevariadic-functionsanonymous-functionfunction-compositionchez-scheme

A Scheme function to compose other functions


I wish to create a function that can compose two functions into one. Given g and f, it should create a function h provides the output of f as input to g. Here was my result:

(define (compose g f)
    (lambda (.args)
        (g (apply f (if (atom? .args) (list .args) .args)))))

The following snippet works with compose.

(display ((compose add1 add1) 3))

However, I cannot figure out why the example below won't work. I provided the error message that I got below, and I am using Chez Scheme 9.5.2. The error here probably has to do with the variadic nature of compose, but that's just an idea. Does anyone know why my second test isn't working as expected?

(define (reduce f lst seed)
    (if (null? lst) seed
        (f (car lst) (reduce f (cdr lst) seed))))

(define product-of-incremented-list
    (compose
        (lambda (lst) (reduce * lst 1))
        (lambda (lst) (map add1 lst))))

(display (product-of-incremented-list '(3 4 5)))
; I was expecting 120, because (3 4 5) -> (4 5 6) and (4 5 6) -> 120
; I'm getting an error instead:
; Exception: incorrect number of arguments to #<procedure at compose.scm:285>

Solution

  • You are trying to use .args to mean "a list of arguments", perhaps by analogy with (lambda (x . more) ...), but this is not correct. .args is just a symbol like any other, so you have written a function that takes one argument named .args. Then when you discover your argument is a list, you (apply f '(3 4 5)). But the lambda you are using for f does not expect three arguments, it expects a single list argument. Thus the error.

    Instead, the way to say "a list of arguments" is

    (lambda args
      ...)
    

    In general, what you write after the lambda is how to treat the input argument list. If you put a list like (x y) there, then the argument list is expected to be two components, x and y. For (x . more), the first element is named x, and then the rest of the list is named more. If you just want the list as-is, you simply give it a name: here, args.

    Your second issue is this checking of whether the arguments are a list or an atom. This is silly: you can be certain it will always be a list, because you do not break down the input parameters. So,

    (define (compose f g)
      (lambda args
        (f (apply g args))))