我绝对不是一个编译器专家,所以我不确定“提升”这个名字是否适用于这个问题。当你不知道你正在寻找的术语时,谷歌搜索也会有点困难。
我正在编写一个纯ClojureScript实现来处理DOM。有点像是Svelte和React的混合体。在它里面,我有一个宏,它将一个常见的Hiccup形式重写成一个“创建”函数和一个“更新”函数。为了使这个宏高效,我想能够创建ns级别的“变量”。
这可能看起来像这样
(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中没有“身份”,运行时无法确定这是否是之前相同的同名片段,或者它已经被更新(无论是通过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?
。