Re-implementing Clojure’s comp

While working through Clojure for the Brave and True, I came across an explanation of Clojure’s comp function and some basic implementations of versions of comp that take only two arguments or three arguments. The tutorial challenged me to try to completely re-implement comp to accept an arbitrary number of function arguments, and that got me thinking that it would be a very good exercise to try in Clojure.

The book had an implentation of the two-argument comp that looked something like this;

(defn two-comp [f g]
  (fn [& args]
    (f (apply g args))))

I worked off of that as a base for a while, but kept running into trouble. I kept running into rather complicated functions-of-functions that seemed to be returning functions that operated on the input functions, rather than the actual arguments, which was not what we were looking for. It’s a bit hard to wrap your head around this, since it’s very meta and we’re talking about a function that has functions as arguments and returns a function of functions as its result.

I decided to take a stab at this in Python, my go-to language.

What I came up with ended up looking like this:

def clojure_comp(*args):
    def wrap_function(to_wrap, wrapper):
        def return_function(*inner_args):
            # Here is where we actually call the function
            return wrapper(to_wrap(*inner_args))
        return return_function

    return reduce(wrap_function, reversed(args))

This ended up working out very nicely, using string maniuplation as a test:

>>> clojure_comp(lambda x: x.upper(), lambda x: x.strip(), lambda x: x.replace('a', 'b'))(" abcdefgh   ")
'BBCDEFG'

So I gave this another shot in Clojure.

(defn mycomp [& args]
  "A function that takes an arbitrary number of functions and returns a function that 
  applies each of those functions, last first, to the input arguments."
  (defn wrapper-function [wrapped wrapper]
    (fn [& inner-args] (wrapper (apply wrapped inner-args)))
    )

  (reduce wrapper-function (reverse args))

  )

And it does work—

user=> ((mycomp clojure.string/trim clojure.string/lower-case) "   this IS a test   ")
"this is a test"

But ultimately, I think it might be a bit too Pythonic rather than right for Clojure. I don’t tend to see too many functions that declare another named function within themselves, and I also feel like this is the kind of function that might better use recursion or loop than reduce, which I feel like I’m falling back on as a replacement for Python’s convenient for iteration.

That being said, it could still be a valuable way of doing it. I recently got complimented by a Javascript developer for my use of reduce in some Javascript I was writing for the Sefaria project. He said reduce and map were functional elements of JS that even most seasoned Javascript developers don’t use, so he was surprised that I was—but to me, languages like Clojure make it seem quite natural to use, and much more elegant in many cases than iteration and mutation.