如何从源文件而不是REPL中使用macroexpand-1

时间:2017-05-06 00:49:08

标签: clojure

在不使用REPL时使用macroexpand-1测试Clojure宏的正确方法是什么?

2 个答案:

答案 0 :(得分:3)

问题是你的deftest之外的表达式是在编译时运行的,而*ns*是绑定的,而deftest内的表达式是在运行时运行的,*ns*未绑定。

为什么这很重要?因为macroexpand需要解析当前命名空间中的符号iiinc以确定它是否为宏,并且如果它是宏,则找到它的定义,以便调用它。所以,你看到你的macroexpand在编译时工作但不是在运行时。

解决方案是什么?当然不要在编译时运行你的测试!相反,您应该对表单进行正确的命名空间限定,以便它们不依赖于*ns*的编译时方便性。你可以手写,写

(deftest t-stuff
  (println "(macroexpand-1 '(my.ns/iiinc 2))  =>" (macroexpand-1 '(my.ns/iiinc 2)))))

但是,正确的解决方案是在引用用于以后评估的表单时执行始终所做的操作,就像编写宏时一样:使用语法引用,而不是常规引用。这样编译器在编译时为您计算出预期的命名空间,并将其插入表单中,以便它在运行时仍然存在:

(deftest t-stuff
  (println "(macroexpand-1 `(iiinc 2))  =>" (macroexpand-1 `(iiinc 2)))))

答案 1 :(得分:1)

假设我们要测试一个为任何值添加3的宏:

(defmacro iiinc [x]
  `(+ 3 ~x))

我不喜欢在REPL工作,而是喜欢使用我喜欢的文本编辑器/ IDE来开发代码,并使用the lein test-refresh plugin来持续运行我的单元测试。但是,在尝试使用macroexpand-1迭代开发新宏时,这不起作用。

问题似乎是macroexpand-1deftest宏之间存在一些冲突。因此,解决方案是避免在macroexpand-1表单中使用(deftest ...)。但是,它在deftest之外工作得很好,即使它仍然在单元测试源文件中。这是一个例子:

; source file tst.clj.core

(newline)
(println "This works when not in (deftest ...)")
(println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))

(deftest t-stuff
  (newline)
  (println "But it is broken inside (deftest ...)")
  (println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))

  (newline)
  (println "However, we can use the macro itself fine in our tests")
  (println "  (iiinc 2) =>" (iiinc 2))
  (is (= 5 (iiinc 2))))  ; unit test works fine

以上结果是:

This works when not in (deftest ...)
(macroexpand-1 '(iiinc 2))  => (clojure.core/+ 3 2)

Testing tst.clj.core

But it is broken inside (deftest ...)
(macroexpand-1 '(iiinc 2))  => (iiinc 2)

However, we can use the macro itself fine in our tests
  (iiinc 2) => 5

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.