Clojure macro puzzle: an expanding sequence in args macros

This is not my “production code," but a simplification of the problem for illustration. Also, the title of this question is misleading, as it recalls the ~ @ extension, which I understand, and which might not be a problem. Please suggest a better question title if you can.

Given a macro like this:

(defmacro my-add [x & ys] `(+ ~x ~@ys )) 

Now let's say that we have a list:

 (def my-lst '(2 3)) 

Now I want a function that uses my-add so that I can pass my-lst as arg, i.e.

 (call-my-add 1 my-lst) 

I define a function in what seems obvious:

 (defn call-my-add [x ys] (apply my-add (cons x ys))) 

But:

 java.lang.Exception: Can't take value of a macro: #'user/call-my-add (repl-1:60) 

I tried all kinds of wild tricks to make the call-my-add function work using evaluations, applying and even defining call-my-add as a macro, but they all give similar ClassCastExceptions.

Is there a way out of this?

+4
source share
2 answers

No. Macros do not, cannot, will never have access to the actual runtime values ​​contained in their arguments, and therefore cannot combine them into an extension. All they get is the character (s) you pass them, in this case my-list . The "way around this" is to define my-add as a function, and then (optionally) have a macro that calls this function to generate its code.

I wrote a blog post about this recently that you can find enlightenment.

You can do this with evals if you want, but this is a terrible idea in almost every case:

 (let [my-list '(1 2)] (eval `(my-add 5 ~@my-list ))) 
+6
source

What a great example shows that macros are not first-class citizens in Clojure (or any Lisp that I know of). They cannot be applied to functions, stored in containers or passed to functions, etc. In exchange for this, they gain control when and if their arguments are evaluated.

What happens with macro expansion time must remain in the macro time distribution. Therefore, if my-add is evaluated at the time the macro expands, and you want to use apply , then you will need ... another macro; to apply.

 (defmacro call-my-add [x ys] `(my-add ~@ (cons x ys))) 

Macros are somewhat contagious in this way.

PS: I am not in my answer, so please edit if you see an error in this example (or I will fix it when I return)

+2
source

Source: https://habr.com/ru/post/1343365/


All Articles