我有一个函数 my-plus-in-macro ,它包含一个名为 my-macro 的宏。但 my-macro 是在 my-plus-macro 定义之后声明的。我使用声明来避免无法解决的符号错误。
我在我的代表中输入这些代码。
(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))
然后,如果执行此
,我希望得到3(my-plus-in-macro 1 2)
但我得到了,
ArityException Wrong number of args (1) passed to: user$my-macro clojure.lang.AFn.throwArity (AFn.java:437)
但是,如果我在repl中再次执行 my-plus-in-macro 定义
(defn my-plus-in-macro [x y]
(my-macro (my-plus x y)))
然后我执行此操作 (my-plus-in-macro 1 2)
正是我的期望,3
首次执行(my-plus-in-macro 1 2)会发生什么?
不知何故,当我第一次定义 my-plus-in-macro 时,clojure会看到 my-macro 符号,但它还不知道我打算将它用作宏的名称。
虽然我在定义 my-plus-in-macro 后立即将 my-macro 定义为符号,但clojure仍然不知道 my -macro 是宏的符号。这就是我得到ArityException的原因。
但是,不知怎的,当我用相同的代码重新定义 my-plus-in-macro 时,Clojure现在知道 my-macro 是一个宏并将其视为这样,然后我的代码就可以了。
当然,如果我在 my-plus-in-macro 定义之前添加 my-macro 定义,我不会有任何异常。但这是否意味着我不能使用declare为宏保留一个符号?
修改
根据@xsc的回答,这些代码有效。 我只需要通过提供:form和:env
,使用“正确”方式在 my-plus-in-macro 定义中调用 my-macro(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
修改-0
@xsc你是对的,我不能使用声明来转发宏的符号。我必须在任何使用它的函数之前放置宏定义。
所以,这是正确的代码
(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
答案 0 :(得分:9)
宏在编译时进行评估,这意味着编译器/阅读器必须知道哪些符号代表宏,哪些符号代表宏。只需像你一样使用declare
,你就不会提供这些信息,这意味着你不会用其评估值替换宏调用,而是在你的内容中引用宏函数码。
换句话说:读者遇到my-macro
的那一刻,第一次不知道什么比留在那里更好。由于任何宏只是一个函数(:macro
元数据设置为true
),因此一旦在下面进一步定义,这不会造成问题。
您可以通过以正确的方式调用宏函数来检查这一事实,提供通常会隐式传递的&form
和&env
参数:
(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]
请注意,(+ 1 2)
形式在传递给“宏”之前会被评估,因为我们在这里处理一个普通的函数。
您可以使用前面提到的:macro
元数据将符号声明为宏,但这只会在首次调用时导致异常,因为实际调用是在var绑定到任何内容之前:
(declare ^:macro my-macro)
(defn my-function
[x]
(my-macro x))
;; => IllegalStateException: Attempting to call unbound fn: #'user/my-macro ...
TL; DR:声明一个宏将无法工作,因为无论它在函数内部嵌套有多深,它都会在读取器/编译器遇到它时被调用。