dagda1
dagda1

Reputation: 28770

misunderstanding of variable arguments type

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?

Upvotes: 1

Views: 136

Answers (2)

Thumbnail
Thumbnail

Reputation: 13473

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))

Upvotes: 2

Mark Karpov
Mark Karpov

Reputation: 7599

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.

Upvotes: 1

Related Questions