Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

wrong number of args when using forward declared macro

Tags:

clojure

I have a function my-plus-in-macro that contains a macro named my-macro. But my-macro is declared after my-plus-macro definition. I use declare to avoid unable-to-resolve symbol error.

I type these codes in my repl.

(declare my-macro my-plus)


(defn my-plus-in-macro [x y]
    (my-macro (my-plus x y)))


(defmacro my-macro [my-fn]
    `~my-fn)


(defn my-plus [x y]
   (+ x y))

Then, I expect to get 3 if execute this

(my-plus-in-macro 1 2)

but instead I get,

ArityException Wrong number of args (1) passed to: user$my-macro  clojure.lang.AFn.throwArity (AFn.java:437)

But, if I execute this my-plus-in-macro definition again in the repl

(defn my-plus-in-macro [x y]
    (my-macro (my-plus x y)))

then I execute this (my-plus-in-macro 1 2)

Just what I expect, 3

What happens in my first execution of (my-plus-in-macro 1 2)?

Somehow, when I define my-plus-in-macro for the first time, clojure sees my-macro symbol but it doesn't know yet that I intend to use it as a name for a macro.

Although I have define my-macro as a symbol right after I define my-plus-in-macro, clojure still doesn't know that my-macro is a symbol for a macro. That's why I get ArityException.

But, somehow, when I redefine my-plus-in-macro with the same code, Clojure now knows that my-macro is a macro and treats it as such and then my code works.

Of course I don't get any exception if I put my-macro definition before my-plus-in-macro definition. But does that mean I can not use declare to reserve a symbol for a macro?

EDIT

based on answer from @xsc, these code works. I just have to call my-macro in my-plus-in-macro definition using "correct" way by supplying :form and :env

(declare my-macro my-plus)


(defn my-plus-in-macro [x y]
    (my-macro :form :env (my-plus x y)))


(defmacro my-macro [my-fn]
    `~my-fn)


(defn my-plus [x y]
   (+ x y))


(my-plus-in-macro 1 2)
3

EDIT-0

@xsc you are right, I can not use declare to forward-declare symbols for macros. I must put macro definition before any function that use it.

so, this is the right code

(declare my-plus)

(defmacro my-macro [my-fn]
    `~my-fn)

(defn my-plus-in-macro [x y]
    (my-macro (my-plus x y)))

(defn my-plus [x y]
   (+ x y))

(my-plus-in-macro 1 2)
3
like image 229
mavbozo Avatar asked Jan 10 '23 22:01

mavbozo


1 Answers

Macros are evaluated at compile-time which means the compiler/reader has to know which symbols represent macros and which don't. By simply using declare like you do you're not supplying that information which means that instead of replacing the macro call with its evaluated value you'll have a reference to the macro function in your code.

In other words: the moment the reader encounters my-macro the first time it does not know any better than just leave it there. Since any macro is just a function (with the :macro metadata set to true) this does not pose a problem once it is defined further below.

You can check this fact by calling the macro function the "correct" way, supplying &form and &env parameters that would usually be implicitly passed:

(declare my-macro)

(defn my-function
  [x]
  (my-macro :form :env x))

(defmacro my-macro
  [x]
  (vector &form &env x))

(my-function (+ 1 2)) ;; => [:form :env 3]

Note that the form (+ 1 2) is evaluated before being passed to the "macro" since we're dealing with a plain-old function here.

You can declare a symbol as a macro using the aforementioned :macro metadata but this will only result in an exception when it is first called since the actual call is before the var is bound to anything:

(declare ^:macro my-macro)

(defn my-function
  [x]
  (my-macro x))
;; => IllegalStateException: Attempting to call unbound fn: #'user/my-macro ...

TL;DR: Declaring a macro will not work since it is called the moment it is encountered by the reader/compiler, no matter how deeply nested inside a function it is.

like image 169
xsc Avatar answered Jan 17 '23 21:01

xsc