2011-11-04 15 views
10

私は2つのプロトコルがあるとします。どのようにClojureプロトコルを別のプロトコルに拡張できますか?

(defprotocol A 
    (f [this])) 

(defprotocol B 
    (g [x y])) 

をそして、私は、プロトコルAをサポートするすべてのインスタンスに、プロトコルBを拡張したい:

(extend-protocol A 
    String 
    (f [this] (.length this))) 

(extend-protocol B 
    user.A 
    (g [x y] (* (f x) (f y)))) 

主な動機は、すべてに別々にBを拡張することを避けるためでありますAが拡張される可能性のあるクラス、または他の人がAを拡張する可能性のある未知の将来のクラスにまで拡張することができます(例えば、Aが公開APIの一部であった場合など)。

しかし、これは動作しません - あなたは、次のような何かを得る:

(g "abc" "abcd") 
=> #<IllegalArgumentException java.lang.IllegalArgumentException: 
No implementation of method: :g of protocol: #'user/B found for 
class: java.lang.String> 

がすべてで、このことは可能ですか?そうでない場合は、同じ目標を達成するための賢明な回避策がありますか?

答えて

7

gfの形で実装できると思います。そうであれば、プロトコルBなしで必要なすべての多型性があります。

私は何を意味するfが多型であることを考えると、次のようである、そして

(defn g [x y] 
    (* (f x) (f y))) 

は、プロトコルAを実装して、すべてのタイプをサポートして機能gが得られます。

プロトコルが最下部にある場合、プロトコル機能(またはプロトコル自体を使用する他の機能)でのみ定義される単純な関数によって、名前空間/ライブラリ/プログラム全体が非常に多態性、拡張性および柔軟性が高くなります。

シーケンスライブラリはこれの素晴らしい例です。簡略化された2つの多型関数firstrestがあります。残りのシーケンスライブラリは通常の関数です。

+0

ありがとう。私はこれが私の場合の最良のアプローチだと思っています - シーケンスライブラリとの類推はここでもうまくいきます! – mikera

9

プロトコルはタイプではなく、継承をサポートしていません。プロトコルは本質的に、関数定義の名前付き集合(およびそれらの関数が呼び出されるときのディスパッチ機構)です。

すべて同じ型の実装を持つタイプが複数ある場合は、単に共通の関数を呼び出すことができます。あるいは、メソッドマップを作成し、そのマップに各タイプのextendを作成することもできます。例:

 
(defprotocol P 
    (a [p]) 
    (b [p])) 

(deftype R []) 
(deftype S []) 
(deftype T []) 

(def common-P-impl 
    {:a (fn [p] :do-a) 
    :b (fn [p] :do-b)}) 

(extend R 
    P common-P-impl) 
(extend S 
    P common-P-impl) 
(extend T 
    P common-P-impl) 

実際のシナリオについてさらに詳しく説明すると、正しいアプローチを提案できる場合があります。

+0

これは私の解決策でもあります。私は 'map'を使って重複を取り除くことができると思います。 '(map#(extend%P common-P-impl)[R S T])' – KingCode

+0

残念ながら、 'doseq'や囲み' doall'を使うべきです:map is lazy .. – KingCode

0

あなたがしようとしていることを完全には理解していませんが、clojureのマルチメソッドがあなたの問題のより良い解決策になるかどうか疑問です。

1

私の知るところでは、プロトコルは実際にプロトコルを拡張することができます。 私はここに例作られた:例ではhttps://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

を、我々は(go-fastにそのメンバーを可能にする)Erinaceinaeプロトコルによって拡張される(そのメンバーはdreamを行うことができ)Animaliaプロトコルを持っています。

Erinaceinaeプロトコルの一部であるレコードHedgehogがあります。レコードのインスタンスはdreamgo-fastの両方になります。そうでない本は言いながら中

(ns extproto.core 
    (:gen-class)) 


(defprotocol Animalia (dream [this])) 

(defprotocol Erinaceinae (go-fast [this])) 

(extend-protocol Animalia 
    extproto.core.Erinaceinae 
    (dream [this] "I dream about things.")) 

(defrecord Hedgehog [lovely-name] 
    Erinaceinae 
    (go-fast [this] (format "%s the Hedgehog has got to go fast." (get this :lovely-name)))) 



(defn -main 
    [& args] 
    (let [my-hedgehog (Hedgehog. "Sanic")] 
    (println (go-fast my-hedgehog)) 
    (println (dream my-hedgehog)))) 

;1> Sanic the Hedgehog has got to go fast. 
;1> I dream about things. 
1

実際に何が別のもの

(extend-protocol TaxedCost 
    Cost 
    (taxed-cost [entity store] 
    (* (cost entity store) (+ 1 (tax-rate store))))) 

するだけで拡張プロトコルからあなたを妨げるものはない

(extend-protocol TaxedCost 
    Object 
    (taxed-cost [entity store] 
    (if (satisfies? Cost entity) 
     (do (extend-protocol TaxedCost 
      (class entity) 
      (taxed-cost [entity store] 
       (* (cost entity store) (+ 1 (tax-rate store))))) 
      (taxed-cost entity store)) 
     (assert false (str "Unhandled entity: " entity))))) 

プロトコルによって拡張プロトコルのレシピがあります「Clojureの適用しました」可能。私はこれについてAlex Millerと話し合い、彼は次のように言った:

本当にすべてのケースで動作しません。プロトコルはJavaインタフェースを生成し、そのインタフェースにプロトコルを拡張できます。 問題は、すべてのプロトコルインプリメンテーションがインライン宣言を使用してそのインターフェイスのみのレコードまたはタイプを実装しているわけではありません((defrecord Foo [a] TheProtocol (foo ...))など)。 extend-typeまたはextend-protocolを使用してプロトコルを実装する場合、これらのインスタンスはプロトコルインターフェイスの拡張によってキャッチされません。だからあなたは本当にこれを行うべきではありません:)

関連する問題