我绝不是编译器专家,因此我不知道“提升”这个名字是否完全正确。如果你不知道你正在寻找的术语,那么在谷歌上搜索就有点困难了。
我正在编写一个纯ClojureScript实现,用于DOM处理。有点像Svelte和React的结合。在其内部,我有一个宏,将常见的Hiccup表格重写为创建一个“创建”函数和一个“更新”函数。为了使这个宏高效运行,我想要能够创建ns级别的“变量”。
以下是这个示例可能的样子
(defn card [{:keys [title body]}]
(<> [:div.card
[:div.card-title title]
[:div.card-body body]
[:div.card-footer
[:div.card-actions
[:button "ok"]
[:button "cancel"]]]]))
因此,<> 宏(称为“片段”)将此重写为两个函数。一个用于创建初始DOM,另一个用于更新实际可以更改的部分。这样做是为了尽可能地避免“差异化”。
关于这个话题不再过多赘述,它基本上生成以下内容
(defn card [{:keys [title body]}]
(fragment-create "dummy-id" [title body]
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
问题是,每次调用card函数时,它都会重新创建函数。由于函数在JS中不具有“身份”,运行时无法判断这依然是之前相同的identical?片段,还是经过更新(通过REPL或热重载)的片段。它必须依赖于某些唯一的标识符。JS运行时必须在每个调用时都分配函数,尽管运行时可能会选择重新使用先前的片段并将其立即丢弃。
所以我想生成的是原始代码中的这个样子
(def fragment-123
(fragment-create
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
(defn card [{:keys [title body]}]
(fragment-use fragment-123 [title body]))
因此,片段“handler”仅创建一次(目前使用deftype,但也可以只是一个映射)。然后在调用card函数时使用。
没有“提升”支持,代码只能在行内生成def
(defn card [{:keys [title body]}]
(do (def fragment-123
(fragment-create
(fn create-fn [state] ...)
(fn update-fn [state] ...)))
(fragment-use fragment-123 [title body])))
这显然是问题所在。因此需要检查片段是否已经定义,等等。reify目前正在这样做,而代码看起来并不令人愉悦。
我在shadow-cljs中实现了一个实验,其中想要使用此功能的宏可以调用一个特殊函数(当前为&env,可能是一个绑定),它基本上允许它们将形式“prepend”到它们当前正在工作的任何顶层形式上。
这是否应该是编译器原生支持的功能?
我知道Clojure也没有这个,但它通过创建类并在需要时访问它们来实现这一点,因此它在这方面做一些类似的事情。
实现可能不同。我只是在想,我最初是否太疯狂了想要做这件事。前一个实现工作得很好,只是最终代码更多,包含了很多额外的检查,因为不能依赖identical?
。