4

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

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

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

defmethod method-a X [x]

调度函数是

(defmulti method-a type)

如果我的 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)
)
4

3 回答 3

2

如果您在类型或类上进行调度,请考虑修改您的代码以使用协议

更详细的答案:

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

根据多方法的文档

派生是由 Java 继承(对于类值)或使用 Clojure 的 ad hoc 层次系统的组合确定的。层次系统支持名称(符号或关键字)之间的派生关系,以及类和名称之间的关系。派生函数创建这些关系,并且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")

好的,这按预期工作,并且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- 但我认为与您设计的“黑客”相比,这是一个更重量级/更危险(猴子补丁?)的解决方案。

于 2012-10-15T13:55:46.793 回答
2

这应该可以使用prefer-methoddoc):

用法:(首选方法multifn dispatch-val-x dispatch-val-y)

当存在冲突时,使多方法更喜欢 dispatch-val-x 的匹配而不是 dispatch-val-y

在你的情况下,你会说:

(prefer-method ns-a/method-a X Y)

这应该会导致为 dispatch-val X in 定义的方法在ns-a某事物同时是 X 和 Y 时被调用,并且如果某事物是 Y 而不是 X 则调用你的方法。

编辑: 原来prefer-method是为了解决调度值不完全匹配的冲突,并且多个父级匹配,没有一个派生另一个。如果方法表中的调度值完全匹配,则始终使用该方法。所以这不会解决 OP 的用例。

于 2012-10-15T16:59:01.307 回答
0

来到这里寻找相同问题的惯用解决方案。因为,根据@noahiz 的说法,习惯上不应该这样做,所以我将发布我们当前的解决方法,以供开发人员自行决定使用。只需通过提供对具体方法的访问权限defn并将代理提供给defmulti.

;;price/basic.cljs

(defn get-final-price-impl [{amount :amount}]
  amount) ;;normally this would be a complicated computation we want to keep DRY

(defmulti get-final-price :type)
(defmethod get-final-price :price/price-basic-tag [price-map]
  (get-final-price-impl price-map))
;;price/foreign-currency.cljs

(defmethod price-basic/get-final-price :price/price-foreign-currency-tag
  [price-map]
  (* (price-basic/get-final-price-impl price-map) (:conversion-rate price-map))) 

PS 建议覆盖类型并递归调用多方法的评论之一。IE,如果Y派生自X,则在ns-a/method-aY 内部我们应该这样做(ns-a/method-a (create-an-X y))。不要这样做。如果ns-a/method-afor X 本身调用其他多方法,则这些方法将被分派给 X 而不是 Y - 可能会出现模糊的逻辑错误。

于 2020-07-06T17:19:14.230 回答