2011-11-04 14 views
10

Supponiamo che io ho due protocolli:Come si può estendere un protocollo Clojure ad un altro protocollo?

(defprotocol A 
    (f [this])) 

(defprotocol B 
    (g [x y])) 

E voglio estendere il protocollo B a tutte le istanze che supportano il protocollo A:

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

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

La motivazione principale è quello di evitare di dover estendere la B a parte a tutti le possibili classi che A può essere estesa a, o anche a future classi sconosciute che altre persone possono estendere A (immaginiamo se A fosse parte di un'API pubblica, per esempio).

Tuttavia questo non funziona - si ottiene qualcosa di simile al seguente:

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

Questo è possibile a tutti? In caso contrario, c'è una soluzione ragionevole per raggiungere lo stesso obiettivo?

risposta

7

Mi sembra che sia possibile implementare la funzione g in termini di f. Se questo è il caso, hai tutto il polimorfismo necessario senza il protocollo B.

Quello che voglio dire è la seguente, visto che f è polimorfica, quindi

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

produce una funzione di g, che supporta tutti i tipi, che implementa il protocollo A.

Spesso, quando i protocolli sono in fondo, le funzioni semplici definite solo in termini di funzioni del protocollo (o su altre funzioni che utilizzano il protocollo stesso) rendono l'intero spazio dei nomi/libreria/programma molto polimorfico, estensibile e flessibile.

La libreria di sequenze è un ottimo esempio di questo. Semplificato, esistono due funzioni polimorfiche, first e rest. Il resto della libreria di sequenze è funzioni ordinarie.

+0

Grazie. Penso che questo sia l'approccio migliore nel mio caso: l'analogia con la libreria delle sequenze funziona bene qui! – mikera

9

I protocolli non sono tipi e non supportano l'ereditarietà. Un protocollo è essenzialmente un insieme di definizioni di funzioni (e un meccanismo di invio quando vengono chiamate tali funzioni).

Se si dispone di più tipi con la stessa implementazione, è sufficiente chiamare una funzione comune. In alternativa, puoi creare una mappa dei metodi e extend ogni tipo con quella mappa. Es .:

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

Se si fornisce qualche dettaglio dello scenario attuale, potremmo essere in grado di suggerire l'approccio corretto.

+0

Questa sarebbe la mia soluzione - penso che 'map' possa essere sfruttato per rimuovere la duplicazione, ad es. '(mappa # (estendere% P common-P-impl) [R S T])' – KingCode

+0

Spiacente, 'doseq', o un' doall 'di chiusura dovrebbe essere usato re: mappa essere pigri .. – KingCode

0

Anche se non capisco completamente cosa si sta tentando di fare, mi chiedo se i metodi multimodali di chiodatura sarebbero una soluzione migliore per il tuo problema.

1

Da quello che vedo, i protocolli possono infatti estendere i protocolli. Feci un esempio qui: https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

Nell'esempio, abbiamo un Animalia protocollo (che consentono suoi membri non dream) che si prolunga da un Erinaceinae protocollo (che permette ai suoi membri di go-fast).

Abbiamo un record di Hedgehog che fa parte del protocollo Erinaceinae. La nostra istanza del record può sia dream e 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

in "Clojure applicato" non c'è una ricetta di protocollo che estende dal protocollo

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

realtà nulla vi impedisce di protocollo che estende semplicemente un altro

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

mentre libro dice che non è possibile. Ho parlato con Alex Miller di questo e ha detto quanto segue:

In realtà non funziona in tutti i casi. Il protocollo genera un'interfaccia Java e puoi sicuramente estendere un protocollo a tale interfaccia. Il problema è che non tutte le implementazioni del protocollo implementano quell'interfaccia - solo record o tipi che lo fanno con una dichiarazione inline come (defrecord Foo [a] TheProtocol (foo ...)). Se si sta implementando un protocollo utilizzando extend-type o extend-protocol, tali istanze NON verranno rilevate dall'estensione di un'interfaccia di protocollo. Quindi, non dovresti farlo :)

Problemi correlati