【问题标题】:Clojure macros and default arguments when a macro calls another one宏调用另一个宏时的 Clojure 宏和默认参数
【发布时间】:2017-07-04 01:01:10
【问题描述】:

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

非常感谢。

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

    ~@ 在语法引用上下文中将一系列事物扩展为几个单独的事物。您的 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})
    

    作为额外的改进,您应该将分散注意力的 [& [{...}]] 参数节替换为 [{...}]。它们的行为相同,只是前者允许调用者传递零个参数(用 nil 填充)或任意数量的额外参数,这些都被忽略。如果调用者打算省略参数并获取默认值,则您的版本对调用者来说会稍微方便一些,但如果他们遗漏参数或提供太多参数,将不可避免地导致调试麻烦,不小心。

    【讨论】:

    • 伟大而清晰的解释,以及有用的 cmets,谢谢!我看到可选参数可能会导致一些晦涩的错误。我认为 Stuart Sierra 在 [stuartsierra.com/2015/06/01/…
    猜你喜欢
    • 1970-01-01
    • 2011-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多