我绝不是编译器专家,所以不知道“提升”这个名字是否适合这个话题。当你不知道你正在寻找的术语时,谷歌搜索有点困难。
我在编写一个纯ClojureScript实现用于处理DOM。有点像是Svelte和React的混合。在其中,我有一个宏,将常见的Hiccup形式重新编写成一个创建功能和更新功能。为了使此宏有效地工作,我希望能够创建命名空间级别的“变量”。
这是一个可能的例子
(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]))
因此,这个“处理器”片段只被创建一次(目前使用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
调用,但也可能是一个绑定),这基本上允许它们将在顶部表单之前插入一个表单。
这是否应该是编译器原生支持的功能?
我知道Clojure也没有这个功能,但它好像是通过创建类并在需要时访问它们来实现的,因此它在某种程度上就是这样做的。
实现可能会有所不同。我只是想知道,我最初想要这样做是不是疯了。之前的实现工作得很正常,最终代码更多,额外增加了许多检查,因为不能依赖于identical?
。