如果我为另一个命名空间(一个我无法更改的库)声明一个多方法,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)
)
答案 0 :(得分:2)
这应该可以使用prefer-method
(doc):
用法:( prefer-method multifn dispatch-val-x dispatch-val-y)
原因 更喜欢dispatch-val-x匹配的多方法 发生冲突时发送-val-y
在你的情况下,你会说:
(prefer-method ns-a/method-a X Y)
这应该导致为ns-a
中的dispatch-val X定义的方法,如果某些东西是X和Y,则调用你的方法,如果某些东西是Y而不是X,则调用你的方法。
修改强>
结果prefer-method
用于解决与调度值没有完全匹配的冲突,并且多个父节点与两个父节点都不匹配。如果方法表中的调度值完全匹配,则始终使用该方法。所以这不会解决OP的用例。
答案 1 :(得分:1)
如果您调度类型或类,请考虑修改代码以使用Protocols。
更详细的回答:
如果还注册了精确的子类型,则Clojure的defmulti
不允许您分派到父Java类型。这是故意的(它在Liskov substitution principle辩论中采取了“最不可思议”的一面)。由于Y已经存在一个已注册的多方法,如果你的对象isa?
恰好是Y,那么你将按Y的方法 - 即使它也是一个“X”。在Java中,类可以从多个接口继承,它们只能是一个类型。这就是你在这里遇到的问题。
根据documentation for multimethods
派生是由Java继承(对于类值)或使用Clojure的ad hoc层次结构系统的组合决定的。层次结构系统支持名称(符号或关键字)之间的派生关系,以及类和名称之间的关系。 derive函数创建这些关系,
isa?
函数测试它们的存在。 请注意,isa?
不是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?
)。如果找到多个匹配项,则会检查“dominate”值的类型层次结构。我们在此处看到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
- 但我认为与您设计的“黑客”相比,这是一个更加重量级/危险(猴子修补?)解决方案。
答案 2 :(得分: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)))
P.S。建议使用注释之一来覆盖类型并递归调用多方法。即,如果Y
来自X
,则在ns-a/method-a
内Y是(ns-a/method-a (create-an-X y))
。不要这样做。如果X的ns-a/method-a
本身调用了其他多重方法,则将为X而不是Y调度这些多重方法-可能会出现晦涩的逻辑错误。