什么时候在 Clojure 宏中使用 ~'some-symbol?

When to use ~'some-symbol in Clojure Macro?

当我阅读 The Joy of Clojure 时,我偶然发现了一些代码。

(fn [~'key ~'r old# new#]
                  (println old# " -> " new#)

此声明的确切行为是什么 ~'some-symbol

some-symbol#'~another-symbol 或 gensym 之间的区别?

The Joy Of Clojure:(没看懂)

You’ll see the pattern ~'symbol at times in Clojure macros for selectively capturing a symbolic name in the body of a macro. The reason for this bit of awkwardness[11] is that Clojure’s syntax-quote attempts to resolve symbols in the current context, resulting in fully qualified symbols. Therefore, ~' avoids that resolution by unquoting a quote.

您可以使用 the Literate Threading Macro 在 Tupelo 库中查看示例。我们希望用户键入符号 it 并让宏识别它。这是定义:

(defmacro it->
  "A threading macro like as-> that always uses the symbol 'it' 
   as the placeholder for the next threaded value "
  [expr & forms]
  `(let [~'it ~expr
         ~@(interleave (repeat 'it) forms)
         ]
     ~'it))

这也称为 "anaphoric" 宏。然后用户创建如下代码:

(it-> 1
      (inc it)                                  ; thread-first or thread-last
      (+ it 3)                                  ; thread-first
      (/ 10 it)                                 ; thread-last
      (str "We need to order " it " items." )   ; middle of 3 arguments
;=> "We need to order 2 items." )

用户在他们的代码中包含特殊符号 it,这是宏所期望的(& 在这种情况下是必需的)。

这有点特殊。在大多数情况下,无论用户选择什么符号,您都希望宏起作用。这就是为什么大多数宏使用 (gensym...) 或带有“#”后缀的 reader 版本的原因,就像这个例子:

(defmacro with-exception-default
  "Evaluates body & returns its result.  In the event of an exception, default-val is returned
   instead of the exception."
  [default-val & body]
  `(try
     ~@body
     (catch Exception e# ~default-val)))

这是 "normal" 的情况,其中宏创建一个 "local variable" e# 保证不与任何用户符号重叠。一个类似的示例显示 spyx 宏创建一个名为 spy-val# 的 "local variable" 来临时保存表达式 expr:

的计算结果
(defmacro spyx
  "An expression (println ...) for use in threading forms (& elsewhere). Evaluates the supplied
   expression, printing both the expression and its value to stdout, then returns the value."
  [expr]
  `(let [spy-val# ~expr]
     (println (str (spy-indent-spaces) '~expr " => " (pr-str spy-val#)))
     spy-val#))

请注意,对于 (println...) 语句,我们看到了与 '~expr 相反的语法——但这是另一天的话题。