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 元数据)

我有一个解决方案可以在这里使用,但它依赖于协议var可以在编译时通过alter-var-root进行更改,即使在直接链接下。这是一个安全的假设吗?

2个答案

+1

选定答案
 
最佳答案

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

这似乎非常不可能改变,但“保证”是一个强烈的词。

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

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))

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

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

...