我绝不是编译器专家,因此我不知道“提升”这个名字是否适用于此处。当不知道你正在寻找的术语时,谷歌搜索会很困难。
我正在编写一个纯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
,但也可以只是一个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?
做很多额外的检查。