2011-11-04 13 views
10

Załóżmy, że mam dwa protokoły:Jak można rozszerzyć protokół Clojure na inny protokół?

(defprotocol A 
    (f [this])) 

(defprotocol B 
    (g [x y])) 

I chcę przedłużyć Protokół B do wszystkich instancji, które obsługują protokół A:

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

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

Podstawową motywacją jest, aby uniknąć konieczności przedłużenia B osobno dla wszystkich możliwe klasy, które można rozszerzyć na, lub nawet na nieznane przyszłe klasy, które inni mogą rozszerzyć A (na przykład, jeśli A był częścią publicznego interfejsu API).

Jednak to nie działa - można dostać coś jak następuje:

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

Czy to w ogóle możliwe? Jeśli nie, czy istnieje rozsądne obejście dla osiągnięcia tego samego celu?

Odpowiedz

7

Wydaje mi się, że można wdrożyć funkcję g pod względem f. Jeśli tak, to masz cały potrzebny polimorfizm bez protokołu B.

Chodzi o to, co następuje, ponieważ f jest polimorficzny następnie

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

dostarcza funkcję g który obsługuje wszystkie rodzaje który realizuje protokół A.

Często, gdy protokoły znajdują się na samym dole, proste funkcje zdefiniowane tylko w kategoriach funkcji protokołu (lub innych funkcji, które same korzystają z protokołu) sprawiają, że cała przestrzeń nazw/biblioteka/program są bardzo polimorficzne, rozszerzalne i elastyczne.

Biblioteka sekwencji jest tego świetnym przykładem. Uproszczone są dwie funkcje polimorficzne, first i. Reszta biblioteki sekwencji jest zwykłymi funkcjami.

+0

Dzięki. Myślę, że to najlepsze podejście w moim przypadku - analogia z biblioteką sekwencji działa tutaj dobrze! – mikera

9

Protokoły nie są typami i nie obsługują dziedziczenia. Protokół jest w istocie nazwanym zbiorem definicji funkcji (i mechanizmem wywołania, gdy te funkcje są wywoływane).

Jeśli masz wiele typów, które mają taką samą implementację, możesz po prostu wywołać wspólną funkcję. Alternatywnie można utworzyć mapę metod i każdy typ z tą mapą. Np .:

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

Jeśli podasz więcej szczegółów na temat swojego rzeczywistego scenariusza, możemy zaproponować prawidłowe podejście.

+0

To też byłoby moje rozwiązanie - myślę, że "mapa" może zostać wykorzystana do usunięcia duplikatów, np. '(mapa # (przedłużyć% P common-P-impl) [R S T])' – KingCode

+0

Niestety, 'doseq' lub załączający' doall' powinien być użyty ponownie: map being lazy .. – KingCode

0

Chociaż nie do końca rozumiem, co próbujesz zrobić, zastanawiam się, czy multimethods clojure byłoby lepszym rozwiązaniem dla twojego problemu.

1

Z tego, co widzę, protokoły mogą rzeczywiście rozszerzać protokoły. zrobiłem przykład tutaj: https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

W przykładzie mamy protokół Animalia (umożliwiających jej członkowie zrobić dream), który jest przedłużony przez Erinaceinae protokołu (który umożliwia swoim członkom go-fast).

Mamy rekord Hedgehog, który jest częścią protokołu Erinaceinae. Nasze wystąpienie rekordu może zarówno dream i go-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

w „Clojure zastosowano” istnieje przepis protokołu rozciągającego się przez protokół

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

faktycznie nic nie uniemożliwia protokołu rozszerzającego po prostu jeszcze jedną

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

natomiast książka mówi, że nie jest możliwy. Rozmawiałem o tym z Alexem Millerem i powiedział:

To naprawdę nie działa we wszystkich przypadkach. Protokół generuje interfejs Java i możesz na pewno rozszerzyć protokół do tego interfejsu. Problem polega na tym, że nie każda implementacja protokołu implementuje ten interfejs - tylko rekordy lub typy, które robią to za pomocą deklaracji wbudowanej, takiej jak (defrecord Foo [a] TheProtocol (foo ...)). Jeśli implementujesz protokół, używając extend-type lub extend-protocol, te instancje NIE zostaną przechwycone przez rozszerzenie interfejsu protokołu. Naprawdę nie powinieneś tego robić :)