当定义了默认参数时,我遇到了一对Clojure宏的问题。
在以下情况下有2个宏,其中mm02调用mm01:
(defmacro mm01
[ & [ { :keys [ f1 ] :or { f1 long } :as opts } ]]
`(let []
(println "(2) ~f1" ~f1)))
(defmacro mm02
[ & [ { :keys [ f1 ] :as opts } ]]
`(let []
(println "(1) ~f1" ~f1)
(mm01 ~@opts)))
评估:
(mm02 { :f1 byte })
打印出来:
(1) ~f1 #function[clojure.core/byte]
(2) ~f1 #function[clojure.core/long]
但是,我原以为:
(1) ~f1 #function[clojure.core/byte]
(2) ~f1 #function[clojure.core/byte]
我做错了什么或者我错过了什么?
顺便说一下,评价:
(mm01 { :f1 byte })
打印出来:
(2) ~f1 #function[clojure.core/byte]
非常感谢。
答案 0 :(得分:5)
~@
在语法引用上下文中将一系列事物扩展为几个单独的事物。您的opts
绑定是一个映射,它在概念上是一系列映射条目。您可以通过在repl中使用宏将生成的表达式来查看此操作:这通常是查看宏的中间步骤的有用方法,与通过宏上的反复试验进行调试相比较整体。
user=> (let [opts {:f1 'long}]
#_=> `(foo ~@opts))
(user/foo [:f1 long])
请参阅:f1 long
周围的方括号?这就是问题:你的另一个宏期望用地图调用,而不是向量。结果,解构无法找到您正在寻找的密钥。要解决此问题,只需删除@
并使用普通的非引号,而不是拼接不引用。
user=> (let [opts {:f1 'long}]
#_=> `(foo ~opts))
(user/foo {:f1 long})
作为一项额外的改进,您应该使用[& [{...}]]
替换分散注意力的[{...}]
参数节。它们的行为相同,只是前者允许调用者传递零参数(填入nils)或任何数量的额外参数,这些都被忽略。你的版本对于调用者来说非常方便,如果他们意味着省略一个参数并获得默认值,但是如果他们遗漏了一个参数,或者提供太多参数,它们将不可避免地导致他们调试的麻烦很大,不小心。