clojure - 如何创建可配置的宏

标签 clojure macros

假设我们要编写一个宏,其行为取决于某种配置。具体来说,在我的例子中,配置只是一个 bool 值,表示输出代码中的一些优化。更具体地说,转换看起来像这样(用 ==> 表示宏展开):

(transform ...) ==> (transform'-opt ...) ==> (transform' ...) ==> ...

当优化发生时,否则transform'-opt被省略并且transform'用在它的地方。

使用参数进行配置会导致重复过多,所以最好在本地/with-...中配置宏。样式(也可能是全局样式)。我的意思是,使用类似 (with-opt <em><strong>value expr*</strong></em>) 的东西, 在每个 expr 的宏展开期间value用作transform的配置除非这被另一个 with-opt 改变了.到目前为止我选择的解决方案是使用动态变量和 binding通过以下方式:

(require '[clojure.tools.analyzer.jvm :refer [macroexpand-all]])

(def ^:dynamic *opt* true)

(defmacro with-opt [value & body]
  `(do ~@(binding [*opt* value] (mapv macroexpand-all body))))

(defmacro transform [...]
  `(~(if *opt* `transform'-opt `transform') ...))

但是,使用动态变量和macroexpand-all at macroexpansion 对我来说看起来有些不同寻常。我考虑过的其他(也是非常规的)选项包括使用常规 var 和 with-redefs ,一个原子,一个 volatile 并且还将配置隐藏在这样的闭包中:

(let [opt ...]
  (defmacro with-opt ...)
  (defmacro transform ...))

[也许在这种情况下避免突变和手动宏扩展的假想通用方法可能是引入另一个隐式 &config宏中的参数和新的特殊形式 (with-macro-configs [<em><strong>(macro config)*</strong></em>] <em><strong>expr*</strong></em>)这将为各种宏指定用于此参数的值。]

对于这种情况,有什么通用的做法吗?你的方法是什么,为什么?提前致谢。

最佳答案

这种事情用 Clojure 的宏系统很难做到,但如果我们有 macrolet 就会很容易。我记得读过,我认为在 Let Over Lambda 中,通过使用 macrolet(它确实存在于其他 Lisp 方言中),大多数尝试自行遍历代码的代码遍历宏都会变得更加简单和强大.这样,编译器就会为您完成所有困难的工作,包括正确处理阴影和引用等。

举个简单的例子,假设您有一些宏需要一个 bool 参数,并且您希望在词法上下文中它始终为 true。使用 macrolet,您可以编写:

(defmacro change* [code toggle]
  (if toggle
    `(not ~code)
    code))

(defmacro with-toggle [toggle & body]
  `(macrolet [(~'change [code#]
                `(change* ~code# ~~toggle))]
     ~@body))

(with-toggle true
  (change (= 1 2)))

虽然 macrolet 在 Clojure 标准库中不存在,但在 clojure.tools.macro 中有一个几乎的实现就像编译器内置的那样。它不支持一些边缘情况,但对于我曾经想写的每一个合理的宏,它都是正确的。这是它适用于我们的示例案例的证据:

user=> (macroexpand '(with-toggle false (change (= 1 2))))
(do (= 1 2))
user=> (macroexpand '(with-toggle true (change (= 1 2))))
(do (clojure.core/not (= 1 2)))

当然,困难的部分是编写 macrolet 的主体,因为您必须处理两级语法引用。我应该知道我在做什么,但我仍然尝试了三四次才能正确使用这个简单的宏。请记住,每个 ~ 都会转义一个语法引用级别,之后您必须决定下一步做什么:再次转义,使用 gensym# 变量,使用正常报价,或者只是保持一个深度。

关于clojure - 如何创建可配置的宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65723179/

相关文章:

clojure - 数据查询和懒惰

C 中的条件 #define

c - C宏中的#x是什么意思?

c++ - 宏返回值

c++ - #define inside an enum 或如何扩展枚举

Clojure:让作用域和函数返回值

clojure - 如何在深度嵌套的数据结构中查找元素?

dictionary - 有没有一种惯用的方法可以在 Clojure 的 map 中找到匹配的键和值?

clojure - 在 clojure 中,如何将 'and' 应用于列表?

macros - SourceTree点击 ' terminal button'切换终端不工作