2011-11-04 122 views
10

假設我有兩個協議:如何將Clojure協議擴展到其他協議?

(defprotocol A 
    (f [this])) 

(defprotocol B 
    (g [x y])) 

我想協議B延伸到支持協議A所有實例:

(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

在我看來,您可以根據f執行功能g。如果是這種情況,你有沒有協議B所需的所有多態性。

我的意思是以下,鑑於f是多態的,然後

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

得到支撐,其實現協議A所有類型的函數g。通常,當協議處於最底層時,僅通過協議功能(或其他本身使用協議的功能)定義的簡單函數使得整個命名空間/庫/程序具有非常多樣性,可擴展性和靈活性。

序列庫就是一個很好的例子。簡化,有兩個多態函數,firstrest。序列庫的其餘部分是普通函數。

+0

謝謝。我認爲這是對我而言最好的方法 - 與序列庫的類比在這裏很有效! – mikera

9

協議不是類型,也不支持繼承。協議本質上是一個命名的函數定義集合(以及調用這些函數時的調度機制)。

如果您有多種類型都碰巧具有相同的實現,您可以簡單地調用一個常用函數。或者,您可以創建一個方法地圖,並使用該地圖創建每種類型的方法地圖。例如: -

 
(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#(extend%P common-P-impl)[R S T])' – KingCode

+0

對不起,'doseq'或者一個封閉的'doall'應該用於re:map懶。 – KingCode

0

雖然我不完全明白你想要做什麼,但我不知道clojure multimethods是否會成爲你的問題的更好解決方案。

1

從我看到的,協議確實可以擴展協議。 我由這裏的例子:https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

在這個例子中,我們有一個Animalia協議(這允許其成員做dream),其由一個Erinaceinae協議(這允許其成員go-fast)擴展。

我們有一個記錄Hedgehog它是Erinaceinae協議的一部分。我們的記錄實例可以是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

「Clojure的應用」,有一項協議延長協議的配方

(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))))) 

其實沒有什麼能阻止你從簡單的擴展協議,由另一人

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

雖然書上說這不是可能。我已經和亞歷克斯米勒討論過這件事,他說:

它確實不適用於所有情況。該協議會生成一個Java接口,您可以確定將協議擴展到該接口。 問題是,並非每個協議實現都實現了該接口 - 只有使用內聯聲明(例如(defrecord Foo [a] TheProtocol (foo ...)))才能這樣做的記錄或類型。如果您使用extend-typeextend-protocol實施協議,那麼這些實例將不會被協議接口的擴展捕獲。所以,你真的不應該這樣做:)