我绝不是编译器专家,所以我不确定“提升”这个名称是否适合这个场景。如果你不知道你正在寻找的术语,查找相关的信息将非常困难。
我正在编写一个纯 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?
。