我绝不是编译器专家,所以我不知道“提升”这个名字是否甚至接近这个问题的正确方向。当你不知道你正在寻找的术语时,谷歌搜索会变得有点困难。
我正在编写一个纯 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 的函数,另一个是更新实际可以更改的部分。这样做是为了尽量减少“diff”。
在此不进一步详细介绍,这基本上生成
(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]))
因此,片段 "handler" 只创建一次(目前使用 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
调用,也可能是一个绑定)将一个形式“ prepend”到它目前正在处理的任何顶级形式。
这是否应该是编译器原生支持的事物?
我知道 Clojure 本身也没有这个,但它确实通过创建类并在需要时访问来实现,它有点是这样做的。
实现可能不同。我只是想确定,我一开始想要做这个是不是疯了。之前的实现工作正常,只是代码更多,因为不能依赖 identical?
所以需要进行大量的额外检查。