Search code examples
racket

manual compostion of two functions


I try to make my own (compose) function, but I can't get through it.

It needs to work as follow:

let's call that function "manual" - manual needs to take two arguments, and it has to compose them in a way that later we can call our manual like this: ((manual func1 func2) 1) (so the output of the manual needs to be a procedure)

so if the func2 multiplies input by 2, and func1 raise it to the third power, then output of ((manual func1 func2) 1) should be 8 (( 2*1)^3)

I tried to write function like this:

(define (manual func1 func2) 
    (func1 (func2)) ; or f(g), or many other combinations of parenthesis
)

but unfortunately I couldn't came with idea of composing those two.

So I guess it needs to be a bit more abstract/complex than just some manipulation with parenthesis.


Solution

  • I want a function, manual, that accepts two functions, f and g -

    (define (manual f g)
      ...)
    

    It should return a function, call it composition -

    (define (manual f g)
      (define (composition ...)
        ...)
      composition)
    

    When composition is applied to an argument, arg, it should return a result of f applied to arg, where g is applied to f's result -

    (define (manual f g)
      (define (composition arg)
        (g (f arg)))
      composition)
    

    Let's test it -

    ((manual
      (lambda (x) (+ x 1))
      (lambda (x) (* x 2)))
     3)
    
    8 ; (3 + 1) * 2
    

    It would be nice if composition didn't require a name. lambda allows us to define a nameless, anonymous procedure -

    (define (manual f g)
      (lambda (arg)
        (g (f arg))))
    

    If I want to make the composition accept multiple arguments, I can use apply to apply f to all args -

    (define (manual f g)
      (lambda args
        (g (apply f args))))
    
    ((manual * add1) 10 20)
    
    201 ; (10 * 20) + 1
    

    If I want manual to accept any number of functions, I can rewrite the composition to apply all of them in sequence using a left fold -

    (define (manual f . more)
      (lambda args
        (foldl (lambda (f x) (f x))
               (apply f args)
               more)))
    
    ((manual * add1 add1 add1 add1 add1) 10 20)
    
    205 ; (((((10 * 20) + 1) + 1) + 1) + 1) + 1
    

    Above special behaviour is given to the first function in the sequence. If I want to make manual generic, I can ask the caller to handle the specialized behaviour -

    (define (manual . funcs)
      (lambda (init)
        (foldl (lambda (f x) (f x))
               init
               funcs)))
    
    ((manual
      (curry apply *) ; specific behaviour
      add1
      add1
      add1
      add1
      add1)
     (list 10 20)) ; multiple arguments collected in a list
    
    205 ; (((((10 * 20) + 1) + 1) + 1) + 1) + 1
    

    @PeterWinton points out that a typical compose function will apply the functions in right-to-left order. The manual procedure we wrote above applies them left-to-right. It's worth knowing the difference and what's considered common in practice. Writing manual to reflect this behaviour remains an exercise for the reader.