如何强制调度到现有的多方法实现?

Hen*_*gon 5 types clojure multimethod

如果我为另一个命名空间(我无法更改的库)声明一个多方法,ns-a,对于我的类型:

defmethod ns-a/method-a Y [y]
Run Code Online (Sandbox Code Playgroud)

ns-a 中定义了 X 的现有方法:

defmethod method-a X [x]
Run Code Online (Sandbox Code Playgroud)

调度函数是

(defmulti method-a type)
Run Code Online (Sandbox Code Playgroud)

如果我的类型 Y 也是 X,我如何在 Y 的实现中分派到 X 的实现?

编辑:我发现了一个黑客:

(defmethod ns-a/method-a Y [y]
 (do-special-Y-stuff-here y)
    ; now do X stuff with Y:
    ((get-method ns-a/method-a X) y)
)
Run Code Online (Sandbox Code Playgroud)

noa*_*hlz 2

如果您按类型或类进行调度,请考虑修改代码以使用Protocols

更详细的答案:

如果还注册了精确的子类型, Clojuredefmulti不允许您分派到父 Java 类型。这是故意的(在里氏替换原则辩论中它站在“最不令人惊讶”的一边)。由于 Y 已经注册了多方法,如果您的对象isa?恰好是 Y,那么您将使用 Y 的方法 - 即使它也是“X”。在Java中,类可以从多个接口继承,它们只能是一种类型。这就是你在这里遇到的问题。

根据多方法的文档

派生由 Java 继承(对于类值)或使用 Clojure 的临时层次结构系统的组合来确定。层次结构系统支持名称(符号或关键字)之间的派生关系以及类和名称之间的关系。导出函数创建这些关系,并isa?测试它们是否存在。注意,isa?不是instance?

如果您查看 的源代码MultiFn,您会发现 Clojure 始终使用给定多方法分派值(在按类型分派时)的最具体的 Java 类。

请参阅Clojure 1.4.0 源代码中的这一行MultiFn以及具体的实现dominates

在 REPL 上:

user=> (defmulti foo #(class %))
user=> (defmethod foo java.util.RandomAccess [x] "RandomAccess")
user=> (defmethod foo java.util.Vector [x] "Vector")
user=> (defmethod foo java.util.Stack [x] "Stack")
Run Code Online (Sandbox Code Playgroud)

好的,这按预期工作,并且prefer-method不能覆盖类层次结构,因为isa?首先检查 Java 类型。

user=> (prefer-method foo java.util.RandomAccess java.util.Stack)
user=> (foo (new java.util.Stack))
"Stack"
Run Code Online (Sandbox Code Playgroud)

最后,在源代码中检查与MultiFn调度值类型匹配的所有方法键(按照isa?)。如果找到多个匹配项,它将检查类型层次结构中的“主导”值。我们在这里看到Stack占主导地位的RandomAccess

user=> (isa? java.util.Stack java.util.RandomAccess)
true
user=> (isa? java.util.RandomAccess java.util.Stack)
false
Run Code Online (Sandbox Code Playgroud)

现在,如果我定义一个新方法bar如下:

user=> (defmulti bar #(class %))
user=> (defmethod bar Comparable [x] "Comparable")
user=> (defmethod bar java.io.Serializable [x] "Serializable")
Run Code Online (Sandbox Code Playgroud)

由于含糊不清,我得到以下信息:

user=> (bar 1)
IllegalArgumentException Multiple methods in multimethod 'bar' match dispatch value: class java.lang.Long -> interface java.lang.Comparable and interface java.io.Serializable, and neither is preferred  clojure.lang.MultiFn.findAndCacheBestMethod (MultiFn.java:136)
Run Code Online (Sandbox Code Playgroud)

现在,我可以解决这个问题prefer-method

user=> (prefer-method bar Comparable java.io.Serializable)
user=> (bar 1)
"Comparable"
Run Code Online (Sandbox Code Playgroud)

但是,如果我注册一个新方法Long

user=> (defmethod bar Long [x] "Long")
user=> (bar 1)
"Long"
Run Code Online (Sandbox Code Playgroud)

Comparable即使我使用,我也无法返回prefer-method

user=> (prefer-method bar Comparable Long)
user=> (bar 1)
"Long"
Run Code Online (Sandbox Code Playgroud)

这似乎就是你在这里遇到的情况。

请注意,您可以选择remove-method- 但我认为与您设计的“黑客”相比,这是一个更重量级/危险的(猴子修补?)解决方案。