Macros Clojure如何在源文件和Repl中展开宏

Macros Clojure如何在源文件和Repl中展开宏,macros,clojure,read-eval-print-loop,Macros,Clojure,Read Eval Print Loop,我正在学习Clojure中的宏,对宏扩展有疑问。在repl中,当我执行此操作时: user=> (defmacro unless [pred a b] `(if (not ~pred) ~a ~b)) #'user/unless user=> (macroexpand-1 '(unless (> 5 3) :foo :bar)) (if (clojure.core/not (> 5 3)) :foo :bar) 但当我在clj文件中执行相同操作时: (ns scratc

我正在学习Clojure中的宏,对宏扩展有疑问。在repl中,当我执行此操作时:

user=> (defmacro unless [pred a b] `(if (not ~pred) ~a ~b))
#'user/unless
user=> (macroexpand-1 '(unless (> 5 3) :foo :bar))
(if (clojure.core/not (> 5 3)) :foo :bar)
但当我在clj文件中执行相同操作时:

(ns scratch-pad.core
     (:gen-class))

(defmacro unless [pred a b]
    `(if (not ~pred) ~a ~b))

(defn -main [& args]
    (prn
        (macroexpand-1 '(unless (> 5 3) :foo :bar))))
然后运行代码,我得到:

$ lein run
(unless (> 5 3) :foo :bar)
如何让代码与repl打印相同?

发生了什么 这是因为当前名称空间的概念在Clojure中是如何工作的
macroexpand-1
在当前名称空间中展开其参数

在REPL,这将是
用户
;在
user
名称空间中定义宏,然后在该名称空间中调用
macroexpand-1
,一切正常

:gen类的d
名称空间或任何其他名称空间中,编译时当前名称空间就是该名称空间本身。但是,当您稍后调用在该名称空间中定义的代码时,当前名称空间将是此时合适的名称空间。在编译时,它可能是其他名称空间

最后,在应用程序的运行时,默认的当前名称空间是
user

要看到这一点,您可以将宏移动到一个单独的名称空间,还可以定义一个函数
使用宏
并在顶层调用此函数;然后,
:gen类
'd名称空间将需要或使用宏的名称空间。然后,
lein run
将打印您期望的内容一次(在宏的命名空间编译时),并打印未展开的表单两次(当宏的命名空间是主命名空间的
require
d,然后当
-main
调用
时使用宏

解决 Clojure REPL使用
绑定控制当前名称空间
;您也可以这样做:

(binding [*ns* (the-ns 'scratchpad.core)]
  (prn (macroexpand-1 ...)))
您也可以在
-main
中使用语法引号而不是引号:

(defn -main [& args]
  (prn (macroexpand-1 `...)))
                      ^- changed this
(defn -main [& args]
  (in-ns 'scratchpad.core)
  (prn (macroexpand-1 '...)))
                      ^- no change here this time
当然,如果涉及到
以外的符号,除非
,否则必须决定它们是否应该在输出中使用名称空间限定符,并可能在它们前面加上
~'
。这就是要点——语法引号对于生成大部分“与名称空间无关”的代码非常有用(除了方便的语法之外,这也是编写宏的优势所在)

另一个可能的“修复”(在Clojure 1.5.1上测试)是在ns调用
-main
中添加

(defn -main [& args]
  (prn (macroexpand-1 `...)))
                      ^- changed this
(defn -main [& args]
  (in-ns 'scratchpad.core)
  (prn (macroexpand-1 '...)))
                      ^- no change here this time

绑定一样
,通过这种方式,您实际上得到了原始名称空间中原始表单的扩展。

我认为答案的讽刺版本是“修复clojure中的错误并提交请求”。该行为不一致,似乎没有记录或故意。在clojure版本1.3、1.4、1.5中可以重现错误不幸的是,不管是否讽刺,我认为任何实际的拉取请求都将被忽略;相反,您必须签署CA(在实际的纸上)并通过Jira提交修补程序——当函数已经在该名称空间中时,您需要如何在ns中指定它?这种行为非常令人困惑。而且,在没有使用任何unquote的情况下,`应该完全像'。我认为这是一个很好的解决方法,但潜在的问题是一个bug。关于syntax quote(backtick)vs.quote,你错了——在Clojure中,syntax quote会导致非命名空间限定的符号被其命名空间限定的对应符号替换(在读取时命名空间中解决)。这是一种与其他Lisp中的准旋转不同(尽管很相似)的机制。关于问题中描述的行为令人困惑——当然,我承认这一点。我会看看这是不是一个bug,也许会写一个补丁。如果现在发布修补程序版本还不够关键(我想可能不是),这里是目前的解决办法。另外,请注意,我已经说过,我认为这种行为令人惊讶,我将在回答中进一步探讨,称这些片段为“解决办法”。对于你的“怎么样…”问题,我真的没什么可说的了。作为旁注:对于我来说,这是令人惊讶的,因为我预期代码将在哪个名称空间中运行,我认为这与
gen类
上的文档相匹配;很明显,
macroexpand-1
将在不同的上下文中产生不同的结果(取决于当前名称空间中可见的宏)。不用担心。在仔细考虑后,用适当的解释编辑了答案。起初我基本上很惊讶,但现在对我来说,这完全是意料之中的行为。也许运行时默认的当前ns问题可以使用更好的文档。为什么更改名称空间会改变macroexpand/macroexpand-1/clojure.walk/macroexpand-all的行为方式?如果位于不同的命名空间中会阻止扩展呢?此问题的另一个解决方法:(macroexpand-1’(scratch-pad.core/exceller…)-在引用形式中完全限定可以使从另一个命名空间调用时的运行时扩展正常工作