Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In clojure, how to apply a macro to a list?

Tags:

clojure

In clojure, apply cannot be applied to a macro. For instance (apply and [true false]) raises an exception. I was thinking about following workaround:

(defmacro apply-macro[func args] `(~func ~@args)) 

At first glance, it seemed to work pretty well:

(apply-macro and [true 5]); 5 (apply-macro and [true 5 0]); 0 (let [a 0] (apply-macro and [true a])); 0 

But, when I passed to it a variable that points to a vector, it collapsed.

(let [a [true]] (apply-macro and a));  java.lang.IllegalArgumentException:    ;Don't know how to create ISeq from: clojure.lang.Symbol 

What a disappointment!!!!

Any idea how to fix apply-macro?

like image 509
viebel Avatar asked Feb 14 '12 07:02

viebel


2 Answers

You don't.

Macros are expanded during evaluation/compile time, not at runtime, thus the only information they can use are the args passed in, but not what the args evaluate to at runtime. That's why a literal vector works, because that literal vector is there at compile time, but a is just a symbol; it will only evaluate to a vector at runtime.

To have and-like behaviour for lists, use (every? identity coll).

To have or-like behaviour for lists, use (some identity coll).

like image 172
Alex Taggart Avatar answered Nov 08 '22 12:11

Alex Taggart


The problem is that a is just a symbol at compile time. So there's no way for a compile-time macro to see what it contains and do the necessary expansion. As a result, you need to expand the macro at run-time using eval.

One way to do this is just to wrap the macro in a function that calls eval, which can be done with this handy "functionize" macro:

(defmacro functionize [macro]   `(fn [& args#] (eval (cons '~macro args#))))  (let [a [true]] (apply (functionize and) a)) => true 

If you like, you could also define apply-macro in terms of functionize:

(defmacro apply-macro [macro args]    `(apply (functionize ~macro) ~args))  (let [a [true false]] (apply-macro and a)) => false 

Having said all this, I still think the best thing to do is to avoid macros entirely when they are not really needed: they add extra complexity and are best reserved for cases when you really need compile time code generation. In this case you don't: Alex Taggart's answer gives a good example of how to achieve a similar objective without any macros, which is probably more appropriate in most situations.

like image 43
mikera Avatar answered Nov 08 '22 14:11

mikera