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

prefer-methodこれは(doc )を使用して可能になるはずです:

使用法:(prefer-method multifn dispatch-val-x dispatch-val-y)

競合がある場合、マルチメソッドがdispatch-val-yよりdispatch-val-xの一致を優先するようにします

あなたの場合、あなたは言うでしょう:

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

これにより、dispatch-val X inns-aに対して定義されたメソッドは、何かがXとYの両方である場合に呼び出され、何かがYであるがXではない場合にメソッドが呼び出されるはずです。

編集:prefer-methodディスパッチ値に完全に一致するものがなく、複数の親がどちらも他方を派生させていない場合に競合を解決するためのものであることが 判明しました。メソッドテーブルにディスパッチ値が完全に一致する場合は、そのメソッドが常に使用されます。したがって、これはOPのユースケースを解決しません。

于 2012-10-15T16:59:01.307 に答える
2

型またはクラスでディスパッチする場合は、Protocolsを使用するようにコードを修正することを検討してください。

より詳細な回答:

Clojuredefmultiでは、正確なサブタイプも登録されている場合、親の Java タイプにディスパッチできません。これは意図的なものです (リスコフの置換原理の議論で「最小の驚き」の側に立っています)。Y の登録済みマルチメソッドが既に存在するため、オブジェクトisa?が正確に Y の場合、Y のメソッドをヒットします。これは「X」でもあります。Java では、クラスは複数のインターフェイスから継承できますが、厳密に 1 つの型にしかなれません。それがあなたがここでぶつかっているものです。

マルチメソッドのドキュメントごと

派生は、Java 継承 (クラス値の場合) の組み合わせ、または Clojure のアドホック階層システムの使用によって決定されます。階層システムは、名前 (シンボルまたはキーワード) 間の派生関係、およびクラスと名前の間の関係をサポートします。派生関数はこれらの関係を作成し、isa?関数はそれらの存在をテストします。ではないことに注意してください。isa?instance?

のソースを調べると、MultiFnClojure が特定のマルチメソッド ディスパッチ値 (型でディスパッチする場合) で最も具体的な 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")

OK、これは期待どおりに機能し、Java 型を最初に検査するprefer-methodため、クラス階層をオーバーライドできません。isa?

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 に答える