取消引用 Clojure 宏中的列表

unquote a list in Clojure macro

我尝试定义一个宏来从特殊形式的文本中提取信息,[0 => "0\n"],实际上是 Clojure 中的列表。

现在假设我只想使用宏 get-input.

获取其中的 first 部分
(println (get-input [0 => "0\n"])) ; i.e. 0

下面的效果很好。

; this works
(defmacro get-input
  [expr]
  (let [input (first expr)]
    input))

但是当我使用 Syntax-Quote,即反引号时,事情变得混乱了。 只是用 ~ 取消引用 expr 就让我想到了这个。 虽然实际上我从来没有使用 exprsecond 部分,即 =>,但似乎它仍然被评估落后。

CompilerException java.lang.RuntimeException: Unable to resolve symbol: => in this context

; this sucks
(defmacro get-input
  [expr]
  `(let [input# (first ~expr)]
     input#))

我想知道第一个解决方案和第二个解决方案有什么区别。

您收到此异常是因为您试图取消引用 [0 => "0\n"] 表单,这不是有效的 Clojure 代码。您可以通过更改 first 和取消引用操作的顺序来修复它:

(defmacro get-input
  [expr]
  `(let [input# ~(first expr)]
     input#))

宏在编译时展开,宏体被计算。如果它是语法引用的,则仅计算未引用的表达式,但如果没有引用(如在您的第一个宏定义中),则在编译时计算主体。

如果你用你的第一个定义(没有引号)扩展 (get-input [0 => "0\n"]),你将有

> (macroexpand '(get-input [0 => "0\n"]))
0

0 是宏扩展的结果,这意味着对 (get-input [0 => "0\n"]) 的所有调用都将在编译时替换为 0.

使用第二个定义,扩展将类似于(生成的符号将不同)

> (macroexpand '(get-input [0 => "0\n"]))
(let* [input__11966__auto__ (first [0 => "0\n"])] 
  input__11966__auto__)

展开后,Clojure 编译器将计算向量 [0 => "0\n"],如 clojure doc 状态所述:

Vectors, Sets and Maps yield vectors and (hash) sets and maps whose contents are the evaluated values of the objects they contain.

向量的每个元素按顺序求值,这对 0 没问题(求值为数字 0),但对 => 不是已知符号。

可能您正在寻找的是扩展表达式(在您的例子中是向量),没有 对其内容的评估。为此,您需要 quote ~ 的结果,例如

(defmacro get-input-as-str
  [expr]
  `(let [a# (quote ~expr)]
     (map str a#)))

扩展为 - 注意 '[...]:

> (macroexpand '(get-input-as-str [0 => "0\n"]))
(let* [a__12040__auto__ '[0 => "0\n"]] (map str a__12040__auto__))

并给出

> (get-input-as-str [0 => "0\n"])
("0" "=>" "0\n")