在这一点上,我绝不是编译器专家,所以我不知道“提升”这个名字是否适用于这个问题。当你不知道你要找的术语时,搜索会变得很困难。
我正在编写一个纯 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,一个用于更新实际可更改的部分。这是为了避免尽可能多的“diffing”。
关于这一点,不说太多细节,它基本上生成了
(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?
。