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))...)
?
My solution was:
Lets try that for:
fs is a list of the functions:
The first time through the reduce, we set
We create an anonymous function, and assign this to the reduce function's accumulator.
Next time through the reduce, we set
Again we create a new anonymous function, and assign this to the reduce function's accumulator.
fs is now empty, so this anonymous function is returned from reduce.
This function is passed 5 and "hello world"
The anonymous function then:
Here is my solution:
I like A. Webb's solution better, though it does not behave exactly like
comp
because it does not returnidentity
when called without any arguments. Simply adding a zero-arity body would fix that issue though.Here's an elegent (in my opinion) definition of
comp
:The nested anonymous functions might make it hard to read at first, so let's try to address that by pulling them out and giving them a name.
This function
chain
is just likecomp
except that it only accepts two arguments.The definition of
comp
atopchain
is very simple and helps isolate whatreduce
is bringing to the show.It chains together the first two functions, the result of which is a function. It then chains that function with the next, and so on.
So using your last example:
The equivalent only using
chain
(noreduce
) is:At a fundamental level,
reduce
is about iteration. Here's what an implementation in an imperative style might look like (ignoring the possibility of multiple arities, as Clojure's version supports):It's just capturing the pattern of iterating over a sequence and accumulating a result. I think
reduce
has a sort of mystique around it which can actually make it much harder to understand than it needs to be, but if you just break it down you'll definitely get it (and probably be surprised how often you find it useful).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
then factor up some
next replace reduce with a loop which does the same
Consider this example:
When
c
is called:first
comp
's rightmost parameterfn
is applied to thep*
parameters ;then
fn-1
is applied to the result of the previous step ;(...)
then
f1
is applied to the result of the previous step, and its result is returnedYour sample solution does exactly the same.
first the rightmost parameter
(last xs)
is applied to theys
parameters:the remaining parameters are reversed to be fed to
reduce
:reduce
takes the provided initial result and list of functions and iteratively applies the functions to the result: