在 Clojure 宏中评估修改后的参数的惯用方法是什么?

What is the idiomatic way to evaluate a modified argument in a Clojure macro?

假设我的意图是修改传递给宏的 s 表达式的语法树,方法是修改语法树,然后计算修改后的参数。我可以执行以下操作:

(defmacro runargs [args]
  (eval (cons (first args) " buckeroo")))

(runargs (println "hi there"))

现在 感觉 不合时宜,因为我在代码中间敲了一个大 eval

现在稍微修改一下,得出如下:

(defmacro runargs [args]
  `(~@(cons (first args) " buckeroo")))

(runargs (println "hi there"))

这已经解决了 eval 问题。但我仍然觉得这不是很惯用。

我的问题是:在 Clojure 宏中评估修改后的参数的惯用方法是什么?

你给出的两个例子做的事情完全不同。第一个是在 宏扩展时间 评估修改后的 s 表达式,这几乎肯定不是您想要的。

user=> (defmacro runargs-eval [args]
         (eval (cons (first args) " buckaroo")))
#'user/runargs-eval

user=> (macroexpand '(runargs-eval (println "hi there")))
  b u c k e r o o
nil

user=> (defmacro runargs [args]
         `(~@(cons (first args) " buckeroo")))
#'user/runargs

user=> (macroexpand '(runargs (println "hi there")))
(println \space \b \u \c \k \e \r \o \o)

如果您只是评估一个恰好包含对您的宏的调用的 s 表达式,则没有太大区别,但是如果您正在编译使用您的宏的代码(如在正文中lambda),宏展开发生在编译时:

user=> (defn say-hello-eval [x] (runargs-eval (println x)))
  b u c k e r o o
#'user/say-hello-eval

user=> (say-hello-eval "hi there")
nil

user=> (defn say-hello [x] (runargs (println x)))
#'user/say-hello

user=> (say-hello "hi there")
  b u c k e r o o
nil

宏只是接受未计算表达式的函数,return 是修改后的未计算表达式。如果您真的想将表达式作为宏展开的一部分进行求值,那将是一种非常不寻常的情况——通常您的宏只会 return 修改后的表达式,并让 Clojure 编译器在适当的时候处理它的求值。

第二个示例中的语法引号并不是真正必要的 - 语法引号的一般用例是当您将宏参数插入到模板化表达式中时,该表达式包含的符号应解析为名称空间中的某些内容定义了宏。如果您的宏确实是 s 表达式的句法转换,那么您可以像操作任何其他 Clojure 数据结构一样将表达式作为数据进行操作:

(defmacro runargs [args]
  (cons (first args) " buckeroo"))