Stephen Cagle
Stephen Cagle

Reputation: 14534

Is the Macro argument a function?

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

Answers (3)

bmillare
bmillare

Reputation: 4233

First, a couple of points:

  1. Macros are simply functions that receive as input [literals, symbols, or collections of literals and symbols], and output [literals, symbols, or collections of literals and symbols]. Arguments are never functions, so you could never directly check the function the symbol maps to.
  2. (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

Ankur
Ankur

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

mikera
mikera

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

Related Questions