我绝非一个编译器专家,所以我不知道“提升”这个名字是否适用于这个场景。当不知道你查找的术语时,谷歌搜索会变得很困难。
我正在编写一个纯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,但也可以是一个map)。然后,每次调用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
(目前,可以是绑定)中调用的一个特殊功能,基本允许它们“将”一个形式添加到它们正在处理的任何顶级形式中。
这应该是编译器原生支持的功能吗?
我知道Clojure也没有这个特性,但它是通过创建类并在需要时访问它们来实现的,所以它某种程度上是这么做的。
实现可能不同。我只是在试图确定我一开始想要这样做是否疯了。之前的实现运行良好,但最终代码量更多,因为需要很多额外的检查,因为它不能依赖于identical?
。