clojure 的 defmacro 中的多重参数 [英] multiple arity in defmacro of clojure
问题描述
我在 Clojure 中遇到了一个与 defmacro 相关的奇怪问题,我有类似的代码
I encountered a strange problem relating to defmacro in Clojure, I have code like
(defmacro ttt
([] (ttt 1))
([a] (ttt a 2))
([a b] (ttt a b 3))
([a b c] `(println ~a ~b ~c)))
我用 (ttt)
运行,它假设变成 (println 1 2 3)
,并打印1 2 3",但我得到的是
and I run with (ttt)
, it suppose to become (println 1 2 3)
, and print "1 2 3", but what I got is
ArityException Wrong number of args (-1) passed to: t1$ttt clojure.lang.Compiler.macroexpand1 (Compiler.java:6473)
经过一番调查,我明白我应该写
after some investigation, I understand I should write
(defmacro ttt
([] `(ttt 1))
([a] `(ttt ~a 2))
([a b] `(ttt ~a ~b 3))
([a b c] `(println ~a ~b ~c)))
但是为什么第一个版本失败了?而args
太奇怪了,看不懂,-1
是从哪里来的?
but why the first version failed? and args
is too strange to understand, where -1
comes from?
推荐答案
宏有两个隐藏参数
宏有两个隐藏参数 &form
和 &env
,它们提供了有关调用和绑定的附加信息,这些信息是此处导致 arity 异常的原因.要在同一宏中引用其他 arity 版本,请使用准引号扩展.
Macros have two hidden arguments
Macros have two hidden arguments &form
and &env
that provide additional information about invocation and bindings that are the cause of the arity exception here. To refer to other arity versions within the same macro, use quasi-quote expansion.
(defmacro baz
([] `(baz 1))
([a] `(baz ~a 2))
([a b] `(baz ~a ~b 3))
([a b c] `(println ~a ~b ~c)))
user=> (macroexpand-1 '(baz))
(clojure.core/println 1 2 3)
user=> (baz)
1 2 3
nil
Arity 异常消息从计数中减去隐藏参数
您得到 (-1) arity 异常的原因是编译器在生成一般宏用法的错误消息时减去了这两个隐藏参数.对于您的 ttt
的第一个版本,此处的真实信息将是参数数量错误 (1)",因为您提供了一个参数 a
但未提供另外两个隐藏参数通过自我调用.
Arity exception messages subtract the hidden arguments from the count
The reason you get the (-1) arity exception is because the compiler subtracts these two hidden arguments when generating the error message for the general macro usage. The true message here for your first version of ttt
would be "Wrong number of args (1)" because you supplied one argument a
but the two additional hidden arguments were not provided by self-invocation.
在实践中,我建议完全避免使用多元宏.相反,考虑一个辅助函数来代表宏完成大部分工作.事实上,这通常也是其他宏的好习惯.
In practice, I suggest avoiding multi-arity macros altogether. Instead, consider a helper function to do most of the work on behalf of the macro. Indeed, this is often a good practice for other macros as well.
(defn- bar
([] (bar 1))
([a] (bar a 2))
([a b] (bar a b 3))
([a b c] `(println ~a ~b ~c)))
(defmacro foo [& args] (apply bar args))
user=> (macroexpand-1 '(foo))
(clojure.core/println 1 2 3)
user=> (foo)
1 2 3
nil
宏扩展是递归的
由于宏扩展的递归性质,您的第二个 ttt
版本也可以正常工作
user=> (macroexpand-1 '(ttt))
(user/ttt 1)
user=> (macroexpand-1 *1)
(user/ttt 1 2)
user=> (macroexpand-1 *1)
(usr/ttt 1 2 3)
user=> (macroexpand-1 *1)
(clojure.core/println 1 2 3)
所以,
user=> (macroexpand '(ttt))
(clojure.core/println 1 2 3)
这篇关于clojure 的 defmacro 中的多重参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!