Reputation: 14534
I am trying to determine whether a given argument within a macro is a function, something like
(defmacro call-special? [a b]
(if (ifn? a)
`(~a ~b)
`(-> ~b ~a)))
So that the following two calls would both generate "Hello World"
(call-special #(println % " World") "Hello")
(call-special (println " World") "Hello")
However, I can't figure out how to convert "a" into something that ifn? can understand. Any help is appreciated.
Upvotes: 3
Views: 132
Reputation: 4233
First, a couple of points:
(call-special #(println % " World") "Hello")
contains reader macro code. Since reader macros are executed before regular macros, you should expand this before doing any more analysis. Do this by applying (read-string "(call-special #(println % \" World\") \"Hello\")")
which becomes (call-special (fn* [p1__417#] (println p1__417# "world")) "Hello")
.While generally speaking, it's not obvious when you would want to use something when you should probably use alternative methods, here's how I would approach it.
You'll need to call macroexpand-all
on a
. If the code eventually becomes a (fn*)
form, then it is guaranteed to be a function. Then you can safely emit (~a ~b). If it macroexpands to eventually be a symbol, you can also emit (~a ~b)
. If the symbol wasn't a function, then an error would throw at runtime. Lastly, if it macroexpands into a list (a function call or special form call), like (println ...)
, then you can emit code that uses the thread macro ->
.
You can also cover the cases such as when the form macroexpands into a data structure, but you haven't specified the desired behavior.
Upvotes: 2
Reputation: 33637
a
in your macro is just a clojure list data structure (it is not a function yet). So basically you need to check whether the data structure a
will result is a function or not when it is evaluated, which can be done like show below:
(defmacro call-special? [a b]
(if (or (= (first a) 'fn) (= (first a) 'fn*))
`(~a ~b)
`(-> ~b ~a)))
By checking whether the first element of the a
is symbol fn*
or fn
which is used to create functions.
This macro will only work for 2 cases: either you pass it a anonymous function or an expression.
Upvotes: 1
Reputation: 106361
You might want to ask yourself why you want to define call-special?
in this way. It doesn't seem particularly useful and doesn't even save you any typing - do you really need a macro to do this?
Having said that, if you are determined to make it work then one option would be to look inside a
and see if it is a function definition:
(defmacro call-special? [a b]
(if (#{'fn 'fn*} (first a))
`(~a ~b)
`(-> ~b ~a)))
This works because #()
function literals are expanded into a form as follows:
(macroexpand `#(println % " World"))
=> (fn* [p1__2609__2610__auto__]
(clojure.core/println p1__2609__2610__auto__ " World"))
I still think this solution is rather ugly and prone to failure once you start doing more complicated things (e.g. using nested macros to generate your functions)
Upvotes: 4