我绝不是编译器专家,所以不清楚“提升”这个名称是否适用于这个问题。当你不知道要找的术语时,谷歌搜起来真的很困难。
我正在编写一个纯 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
函数时,都会重新创建这些函数。由于在 JavaScript 中函数没有“身份”,运行时无法判断这个从前的同一identical?
片段是否已经被更新(无论是通过 REPL 还是热重载),它会依赖于某个唯一的标识符来区分。JavaScript 运行时也会在每个调用中都分配这些函数,即使运行时可能决定重新使用之前的片段并立即将其丢弃。
所以,我想生成的是像以下这样的东西
(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
,也可以是一个绑定),这个函数基本上允许他们将一个形式“追加”到它目前正在处理的顶层形式上。
这应该是由编译器原生支持的特性吗?
我知道Clojure也没有这个特性,但是它通过创建类并在需要时访问它们,所以在某种程度上做到了这一点。
实现可能会不同。我只是想确定我一开始想要这样做是否疯了。之前实现的方式工作得很棒,只是代码量更大,进行了很多额外的检查,因为它不能依赖 identical?
。