我绝对不是编译器专家,所以我不知道“提升”这个名字是否甚至接近正确的领域。在不知道你寻找的术语时很难进行谷歌搜索。
我正在编写一个纯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]))
所以,“处理器”片段只创建一次(目前使用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
,也可能是绑定),这基本上允许它们将其“prepend”到它们目前正在处理的活动最高级形式。
这应该是编译器本就支持的某种功能吗?
我知道Clojure也没有这个功能,但是通过创建类并在需要时访问它们,它某种程度上是在做这件事。
实现方式可能会有所不同。我只是在尝试确定,我一开始想要这样做是否太过疯狂。以前的做法工作得很好,只是最终代码更多,增加了很多额外的检查,因为不能依赖 identical?
。