Search code examples
clojure

Clojure: Implementing the comp function


4Clojure Problem 58 is stated as:


Write a function which allows you to create function compositions. The parameter list should take a variable number of functions, and create a function applies them from right-to-left.

(= [3 2 1] ((__ rest reverse) [1 2 3 4]))

(= 5 ((__ (partial + 3) second) [1 2 3 4]))

(= true ((__ zero? #(mod % 8) +) 3 5 7 9))

(= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world"))

Here __ should be replaced by the solution.

In this problem the function comp should not be employed.


A solution I found is:

(fn [& xs]
  (fn [& ys]
    (reduce #(%2 %1)
            (apply (last xs) ys) (rest (reverse xs)))))

It works. But I don't really understand how the reduce works here. How does it represent (apply f_1 (apply f_2 ...(apply f_n-1 (apply f_n args))...)?


Solution

  • Let's try modifying that solution in 3 stages. Stay with each for a while and see if you get it. Stop if and when you do lest I confuse you more.

    First, let's have more descriptive names

    (defn my-comp [& fns]
      (fn [& args]
        (reduce (fn [result-so-far next-fn] (next-fn result-so-far))
          (apply (last fns) args) (rest (reverse fns)))))
    

    then factor up some

    (defn my-comp [& fns]
      (fn [& args]
        (let [ordered-fns (reverse fns)
              first-result (apply (first ordered-fns) args)
              remaining-fns (rest ordered-fns)]
        (reduce 
          (fn [result-so-far next-fn] (next-fn result-so-far))
          first-result
          remaining-fns))))
    

    next replace reduce with a loop which does the same

    (defn my-comp [& fns]
      (fn [& args]
        (let [ordered-fns (reverse fns)
              first-result (apply (first ordered-fns) args)]
          (loop [result-so-far first-result, remaining-fns (rest ordered-fns)]
            (if (empty? remaining-fns)
                result-so-far
                (let [next-fn (first remaining-fns)]
                  (recur (next-fn result-so-far), (rest remaining-fns))))))))