当宏调用另一个宏时,Clojure宏和默认参数

时间:2017-07-04 01:01:11

标签: clojure macros

当定义了默认参数时,我遇到了一对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]

非常感谢。

1 个答案:

答案 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)或任何数量的额外参数,这些都被忽略。你的版本对于调用者来说非常方便,如果他们意味着省略一个参数并获得默认值,但是如果他们遗漏了一个参数,或者提供太多参数,它们将不可避免地导致他们调试的麻烦很大,不小心。