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

标签 types clojure multimethod

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

defmethod ns-a/method-a Y [y]

并且在 ns-a 中定义了 X 的现有方法:
defmethod method-a X [x]

调度函数是
(defmulti method-a type)

如果我的 Y 类型也是 X,我如何在 Y 的实现中分派(dispatch)给 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)
)

最佳答案

如果您在类型或类上调度,请考虑修改您的代码以使用 Protocols .

更详细的回答:

Clojure 的 defmulti如果还注册了精确的子类型,则不允许您分派(dispatch)到父 Java 类型。这是故意的(在 Liskov substitution principle 的辩论中,它站在“最小惊喜”的一边)。由于 Y 已经注册了多方法,如果您的对象 isa?正好是一个 Y,那么你就会找到 Y 的方法——即使它也是一个“X”。在 Java 中,类可以从多个接口(interface)继承,它们只能是一种类型。这就是你在这里遇到的问题。

根据 documentation for multimethods

Derivation is determined by a combination of either Java inheritance (for class values), or using Clojure's ad hoc hierarchy system. The hierarchy system supports derivation relationships between names (either symbols or keywords), and relationships between classes and names. The derive function create these relationships, and the isa? function tests for their existence. Note that isa? is not instance?.



如果您查看 MultiFn 的来源,您会看到 Clojure 总是使用给定的多方法调度值最具体的 Java 类(在类型上调度时)。

this line in Clojure 1.4.0 source for 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")

好的,这按预期工作,prefer-method无法覆盖类层次结构,因为 isa?首先检查 Java 类型。
user=> (prefer-method foo java.util.RandomAccess java.util.Stack)
user=> (foo (new java.util.Stack))
"Stack"

最后,在源码中查看MultiFn所有与调度值类型匹配的方法键(根据 isa? )。如果找到多个匹配项,它会检查类型层次结构中的“支配”值。我们在这里看到 Stack支配RandomAccess
user=> (isa? java.util.Stack java.util.RandomAccess)
true
user=> (isa? java.util.RandomAccess java.util.Stack)
false

现在,如果我定义一个新方法 bar如下:
user=> (defmulti bar #(class %))
user=> (defmethod bar Comparable [x] "Comparable")
user=> (defmethod bar java.io.Serializable [x] "Serializable")

由于模棱两可,我得到以下信息:
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)

现在,我可以用 prefer-method 解决这个问题。
user=> (prefer-method bar Comparable java.io.Serializable)
user=> (bar 1)
"Comparable"

但是,如果我为 Long 注册一个新方法
user=> (defmethod bar Long [x] "Long")
user=> (bar 1)
"Long"

我无法返回 Comparable , 即使我使用 prefer-method :
user=> (prefer-method bar Comparable Long)
user=> (bar 1)
"Long"

这似乎是你在这里遇到的。

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

关于types - 如何强制调度到现有的多方法实现?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12888900/

相关文章:

clojure - core.typed 不报告 repl 中的类型错误

clojure - Clojure 中#_ 的目的是什么?

java - 如何比较 Integer 和 int?

clojure查找最后一个元素而不使用last函数

interface - Nim - 创建实现方法的对象序列

clojure - 方案-> Clojure : multimethods with predicates in the methods?

clojure - 遍历向量树

python - 在 python 3.9+ 中,如何使用省略号为内置元组类型编写类型别名? (mypy 错误?)

c# - 泛型方法的可为空参数类型 <T>?

c++ - 我可以通过此类的实例访问类的成员类型吗?