我绝非编译器专家,所以我不知道名字“提升”是否适用于这个情况。当不知道你寻找的术语时,很难进行谷歌搜索。
我正在编写一个纯 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,一个用于更新实际可更改的部分。这样做的目的是尽可能减少“差异”。
不深入更多细节,它基本上生成
(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?
。