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

时间:2012-10-15 03:27:03

标签: 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的实现中调度到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)
)

3 个答案:

答案 0 :(得分:2)

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

  

用法:( 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调度这些多重方法-可能会出现晦涩的逻辑错误。