A solution?
In the most general case, you'd need both a macro and eval
for this purpose of this exercise, which I assume is for learning (please don't actually do this).
For the sake of example, keep shove
as is, and use as a helper to a modified shovem
(defn shove [x form] (eval `(-> ~x ~form)))
(defmacro shovem* [x form]
(if (seq? form)
(if (= 'quote (first form))
`(-> ~x ~(second form))
`(-> ~x ~form))
`(shove ~x ~form)))
Now, shovem*
has the semantics you are looking for
(def move '(count))
(shovem* [1 2 3] count) ;=> 3
(shovem* [1 2 3] (count)) ;=> 3
(shovem* [1 2 3] '(count)) ;=> 3
(shovem* [1 2 3] move) ;=> 3
(let [f count, d [1 2 3]] (shovem* d f)) ;=> 3
The problem (?) with the original macro
user=> (def move '(count))
user=> (defmacro shovem [data form] `(-> ~data ~form))
user=> (macroexpand-1 '(shovem [1 2 3] move))
(clojure.core/-> [1 2 3] move)
user=> (macroexpand-1 '(clojure.core/-> [1 2 3] move))
(move [1 2 3])
user=> (macroexpand-1 '(move [1 2 3]))
(move [1 2 3]) ; same, move is not a macro
That ends the macro expansion phase. Now (move [1 2 3])
is code. What happens when it is evaluated?
user=> (move [1 2 3])
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
If the why isn't obvious, you need to reconsider the evaluation rules. The form (move [1 2 3])
is a list, and move
is not special form or a macro. So this is considered a function invocation of move
on its argument [1 2 3]
. But what is move
?
user=> (type move)
clojure.lang.PersistentList
user=> (ifn? move)
false
So, move
is not a function and doesn't know how to act like one. It's just a list.
user=> (= move (list 'count))
true