1

I am trying to solve a clojure problem where I implement my own comp function.

I have the following expression that works how I expect:

(reduce #(apply %2 [%1]) [1 2 3 4] [rest reverse])

This gives an output of

(4 3 2)

I have tried abstracting this into a function like this:

(((fn [& funcs]
        (fn [& args]
       (reduce #(apply %2 [%1]) args funcs)
      )) rest reverse) [1 2 3 4])

But I get the following error when I run it:

CompilerException java.lang.ClassCastException: clojure.lang.ArraySeq cannot be cast to java.lang.Number, compiling:(/Users/paulcowan/projects/scratch/src/scratch/core.clj:1:1)

To me the only difference that I can see is how that funcs and args are different types than the vectors that I created in the first example.

Why does reduce and apply behave differently in the second example?

1
  • Have you solved your problem? I wonder why you could get such an exception, your code is absolutely valid. Commented Sep 3, 2014 at 16:34

2 Answers 2

2

Simply:

(defn my-comp [& fns]
  (fn [x] (reduce #(%2 %1) x fns)))

giving

((my-comp rest reverse) [1 2 3 4])
;(4 3 2)
  1. As it should, my-comp returns an identity function for an empty argument list.
  2. But it expects all functions, including the first applied, to take a single argument.

To get round (2), adapt it as follows:

(defn my-comp [& fns]
  (if (empty? fns)
    identity
    (let [[f & fs] fns]
      (fn [& args] (reduce #(%2 %1) (apply f args) fs)))))

Apart from (1), this merely rephrases Mark's answer.


For fun ...

We could define my-comp in terms of the standard comp:

(defn my-comp [& fns] (apply comp (reverse fns)))

But it probably makes more sense the other way round, since comp has to reverse its argument list in general:

(defn comp [& fns] (apply my-comp (reverse fns)))

We could even define an argument reverser

(defn rev-args [f] (fn [& args] (apply f (reverse args))))

... which turns a function into one that does the same thing to a reversed argument list.

Then

(def comp (rev-args my-comp))

or vice-versa:

(def my-comp (rev-args comp))
Sign up to request clarification or add additional context in comments.

Comments

1

First of all, I don't get any error messages (nor correct result):

user> (((fn [& funcs]
          (fn [& args]
            (reduce #(apply %2 [%1]) args funcs))) rest reverse) [1 2 3 4])
;; => ()

Difference between the two examples is that in the first one you pass value [1 2 3 4] into reduce, while in the second one you pass [[1 2 3 4]] (because args is meant to keep all arguments of the function as one vector.

This will work:

user> (((fn [& funcs]
          (fn [args]
            (reduce #(apply %2 [%1]) args funcs))) rest reverse) [1 2 3 4])
;; => (4 3 2)

However, to get a function for functional composition that will be able to take any number of arguments, you should write something like this:

user> (defn my-comp [& fncs]
        (fn [& args]
          (reduce #(%2 %1) ; you can omit apply here, as %2 is already function
                           ; and %1 is always one value, as noisesmith noticed
                  (apply (first fncs) args)
                  (rest fncs))))
;; => #'user/my-comp
user> (def my-fnc (my-comp rest reverse))
;; => #'user/my-fnc
user> (my-fnc [1 2 3 4])
;; => (4 3 2)

It will work fine, because only first function should have ability to take many arguments, as others will be applied to value returned by previously called function.

6 Comments

since a function can only return one result, you can take apply out of the reducing function, and just use #(%2 %1)
This my-comp is more clearly written (defn my-comp [& fncs] (apply comp (reverse fncs))). It is just comp in the wrong order.
@A.Webb you just used comp to define my-comp. What's the point in that?
@A.Webb, Good catch, although it is not a problem here, in OP's original example it works the same way (functions get applied from left to right).
@A.Webb The whole point of OPs code was to define his own comp function (I guess without using comp, otherwise he could just do (def my-comp comp)). That's why I'm being picky here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.