2017-07-04 73 views
2

当定义一个默认参数时,我有一对Clojure宏的问题。当一个宏调用另一个宏时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 [email protected]))) 

的评价:

(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] 

非常感谢你。

回答

5

[email protected]在语法引用上下文中将一系列事物展开成几个单独的事物。你的opts绑定是一张地图,它在概念上是一系列的map-entries。您可以通过在repl中使用宏将生成的表达式来查看这个动作:与通过宏观试验和错误调试相比,这通常是查看宏的中间步骤的有用方法整个。

user=> (let [opts {:f1 'long}] 
    #_=> `(foo [email protected])) 
(user/foo [:f1 long]) 

查看方括号:f1 long?这就是问题所在:你的其他宏需要用地图来调用,而不是向量。结果,解构无法找到你正在寻找的关键。要解决这个问题,只需删除@并使用普通的不引号,而不是拼接引号。

user=> (let [opts {:f1 'long}] 
    #_=> `(foo ~opts)) 
(user/foo {:f1 long}) 

作为一个附加的改进,就应该更换,只需[{...}]的分心[& [{...}]]参数节。它们的行为相同,只是前者允许调用者传递零参数(用nils填充)或任何数量的额外参数,这些参数都被忽略。如果他们的意味着省略参数并获得默认值,那么您的版本对于调用者来说非常方便,但是如果他们遗漏了一个参数或者意外地提供了太多的参数,那么它们不可避免地会导致调试的麻烦。

+0

非常明确的解释和有用的意见,谢谢!我发现可选参数可能会导致一些模糊的错误。我认为Stuart Sierra在[https://stuartsierra.com/2015/06/01/clojure-donts-optional-arguments-with-varargs]中评论了与此相关的一些方面, –