我绝非编译器专家,所以不知道“提升”这个名词是否适合此情境。如果你不知道要找的术语,谷歌也难以搜索。
我正在编写一个纯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?
。