Here is my attempt:
Trying with some examples, it works the same way as the built-in one :)
((comp + - -) 1 2 3 4 5)Result:
13
((my-comp + - -) 1 2 3 4 5)Result:
13
((comp clojure.string/upper-case
clojure.string/lower-case
(fn [somestr] (str " " somestr " "))
clojure.string/trim)
"heLLo")
Result:
" HELLO "
((my-comp clojure.string/upper-case
clojure.string/lower-case
(fn [somestr] (str " " somestr " "))
clojure.string/trim)
"heLLo")
Result:
" HELLO "
There is one special case, where my implementation behaves differently from the built-in one, and that is when there are zero functions:
((comp) 1 2 3)Result:
ArityException Wrong number of args (3) passed to: core/identity clojure.lang.AFn.throwArity (AFn.java:429)
((my-comp) 1 2 3)Result:
NullPointerException clojure.core/apply (core.clj:624)So both versions leak some implementation details in this case, and cause an error...
Some final remarks:
- the built-in version interprets the last function first; that's why I had to reverse the parameters
- I encountered a problem, while processing the arguments: for the first function, they are in a form of a sequence, and can thus be applied; for all other functions, they are just raw values, and thus it is necessary to call the function directly (without apply)
While redoing this chapter, I came up with another solution:
ReplyDelete(defn n-comp
[& funs]
(fn [& args]
(loop [actargs args actfuns funs]
(if (not (empty? actfuns))
(recur [(apply (first actfuns) actargs)] (rest actfuns))))))
Although it does not work exactly as the built in version, it solves the second problems (namely, sequence vs raw values) more easily: when recuring it puts the parameters into a vector!