2024 Clojure 状态调查!中分享您的想法。

欢迎!请参阅“关于”页面以获取更多有关如何使用本网站的信息。

+2
编译器

以下示例显示,协议方法 pfoo 似乎没有直接链接。

$ 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 来重置。这里是相关的 问题。如果您希望您的协议monkey patch在 extend 中 survive,您需要更改这个协议的 :method-builders 值。但是,传播 .__methodImplCache 到实际实现有一定的要求。从表面上看,我认为您的代码没有正确处理这个问题(内部缓存从未更新)
2. 在某些情况下(终端用户真正注意到这一点,当你看到在协议调用错误时发生“没有单例方法 ...”错误),协议方法被内联。这意味着您的调用 (pfoo x) 变成了 (.pfoo x),您的monkey patch 被绕过了。尝试向协议方法添加 :inline 元数据以避免这种情况

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

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

我可能发布一个库来帮助库作者做这样的事情。

...