Clojure 如何从源文件而不是REPL使用macroexpand-1
当不在REPL上工作时,使用Clojure 如何从源文件而不是REPL使用macroexpand-1,clojure,Clojure,当不在REPL上工作时,使用macroexpand-1测试Clojure宏的正确方法是什么?假设我们要测试一个向任意值添加3的宏: (defmacro iiinc [x] `(+ 3 ~x)) 我通常更喜欢使用我最喜欢的文本编辑器/IDE来开发代码,并使用它来持续运行我的单元测试,而不是在REPL上工作。但是,当尝试使用macroexpand-1迭代开发新宏时,这不起作用 问题似乎是macroexpand-1和deftest宏之间存在冲突。因此,解决方案是避免在(deftest…表单中使用
macroexpand-1
测试Clojure宏的正确方法是什么?假设我们要测试一个向任意值添加3的宏:
(defmacro iiinc [x]
`(+ 3 ~x))
我通常更喜欢使用我最喜欢的文本编辑器/IDE来开发代码,并使用它来持续运行我的单元测试,而不是在REPL上工作。但是,当尝试使用macroexpand-1
迭代开发新宏时,这不起作用
问题似乎是macroexpand-1
和deftest
宏之间存在冲突。因此,解决方案是避免在(deftest…
表单中使用macroexpand-1
。但是,它在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.
后记 请参阅下面的答案以了解更多信息 关于如何在Clojure中编写宏的更完整讨论:
问题在于
deftest
之外的表达式是在编译时运行的,而*ns*
是绑定的,而deftest
内部的表达式是稍后在运行时运行的,而*ns*
是不绑定的
为什么这很重要?因为macroexpand
需要解析当前名称空间中的符号iinc
,以确定它是否是宏,并查找它的定义(如果是宏)以调用它。因此,您可以看到您的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)))))
这是一个很好的答案,但我认为其中的一些内容应该在问题本身,因为它解释了你试图做的不起作用的事情。如果没有这个答案的背景,没有人能回答你的简短问题。四年后回顾这一点,我现在认为整个答案应该转移到这个问题中。对于“如何使用macroexpand测试宏”的问题,您没有提供任何答案;你只是得出结论说它不起作用。这是一个非常彻底的问题,我认为我的回答提供了一个很好的答案,我只是不明白这是一个怎样的答案。