2024年Clojure调查中分享您的想法!

欢迎!请参阅关于页面以了解如何使用本服务的一些更多信息。

+2
无描述图像 编译器

以下示例中所示,协议方法似乎并不是直接链接的。

$ clj -J-Dclojure.compiler.direct-linking=true
Clojure 1.10.1
user=> (defprotocol Foo (pfoo [this]))
Foo
user=> (defn libfn [x] (pfoo x))
#'user/libfn
user=> (extend-protocol Foo String (pfoo [_] :dude))
nil
user=> (libfn "hello")
:dude
user=> (alter-var-root #'user/pfoo (fn [old] (fn [this] (prn :this) (old this))))
#object[user$eval178$fn__179$fn__180 0x18eec010 "user$eval178$fn__179$fn__180@18eec010"]
user=> (libfn "hello")
:this
:dude

我这么问是因为我正在尝试找到一种通用的方法来修复协议方法以在 sci + graal native-image 中工作。限制条件如下:

  • 在直接链接下也能工作(对于原生使用很重要)
  • 可与第三方库一起使用
    • 避免更改目标协议的源代码(无法添加 ^:dynamic,^:redef 元数据)

我在这里有一个解决方案可以工作这里,但它依赖于在编译时,即使在直接链接下,仍然可以使用 alter-var-root 修改协议变量。这是一个安全的假设吗?

2 答案

+1
无描述图像
选定
 
最佳答案

目前创建协议函数的方式不会编译为 invokeStatic 方法(它们会封装一些数据),因此编译器永远不会将它们编译为对 invokeStatic 静态方法的调用(这是直接链接所做的事情)。

这似乎非常不可能发生变化,但“保证”一词很有分量。

需要注意的是,每次重新评估协议定义时,指向函数的变量都会被重置。

0

编辑

还有其他实现细节需要处理(截至 Clojure 1.10.3),我准备好了根据 Clojure 的变化更新您的实现(即,以下提到的所有内容都依赖于与 Clojure 1.8-1.11 相关的完全未记录的实施细节)
1. 如 hiredman 所述,协议方法通过 extend 被重置。这是相关的 问题。如果您希望您的协议猴子补丁在 extend 中幸存,您需要更改该协议的 :method-builders 值。然而,将 .__methodImplCache 传播到实际实现中存在微妙的要求。从外观上看,我认为您的代码没有正确处理这一点(内部缓存从未更新)。
2. 在某些情况下,协议方法会被 内联(最终用户只有在发现错误时才会注意到这一点,例如在调用协议时出现错误)。这意味着您的调用 (pfoo x) 变成了 (.pfoo x),您的猴子补丁被绕过。尝试为协议方法添加 :inline 元数据以避免这种情况

;; untested
(-> #'pfoo alter-meta assoc :inline (fn [& args] `((do pfoo) ~@args))

请注意,内联必须在编译器看到任何调用之前强制执行--一旦它被内联,就太迟了。

我可能发布一个库来帮助库作者进行这类操作。

...