Clojure宏与宏交互的惊喜

时间:2018-03-19 22:22:42

标签: clojure macros

这个问题与两个宏之间的相互作用有关,一个宏包含在另一个宏中。所描述的两个宏是说明交互的示例,而不是唯一的例子。

考虑一个简单的clojure宏,它会产生副作用,但不会改变其输入。

(defmacro echo [action & args]
  `(fn [tru#] (do (~action ~@args tru#) tru#)))

用法

((echo log/info "result") :foo)

将宏包含在另一个宏中,例如 - >>宏。 我的直觉说如下:

(->> :foo (echo log/info "result") clojure.pprint/pprint)

这不适用于 - >>宏首先插入:foo,它使echo宏留有错误的参数。 解决问题的一种方法是引入额外的括号,但这对我来说似乎很难看:

(->> :foo ((echo log/info "result")) clojure.pprint/pprint)

丑陋我的意思是它闻起来像c宏。

1 个答案:

答案 0 :(得分:4)

这里没有什么令人惊讶的事情,但我认为echo扩展到一个功能正在使它更难理解。 macroexpand可以帮助说明正在发生的事情:

(macroexpand '(->> :foo (echo prn "result") clojure.pprint/pprint))
=> (clojure.pprint/pprint (echo prn "result" :foo))

在这里你可以看到->>正确地将参数线程化到echo表单的第二个/最后一个位置,这不是你想要的,因为echo扩展为一个函数必须调用:foo作为唯一参数。如果我们扩展你的另一个例子:

(macroexpand '(->> :foo
                   ((echo prn "result"))
                   clojure.pprint/pprint))
=> (clojure.pprint/pprint ((echo prn "result") :foo))

您可以在线程化后看到,echo来电形成为((echo prn "result") :foo),就像上面的工作示例一样。这里的宏之间没有奇怪的交互,只是语法混乱,因为echo 扩展为必须调用的函数

如果将echo扩展为代码而不是匿名函数,我认为这将更清晰/更清晰(从宏扩展的角度来看)。但是我认为如果没有宏,你可以达到同样的效果,这通常是可取的:

(defn echo [f x & args]
  (do (apply f x args)
      (last args))

(->> :foo
     (echo prn "result")
     (clojure.pprint/pprint))
;; "result" :foo
;; :foo
;; => nil

虽然还有其他方法可以“窥探”线程宏中的值。试试这个例子:

(-> :foo
    (doto (prn "result"))
    (clojure.pprint/pprint))
;; :foo "result"
;; :foo
;; => nil