2010-05-31 18 views
10

È possibile che mi sia sfuggito l'intero punto sui protocolli, ma la mia domanda è: i protocolli possono essere utilizzati per dettare come iterare una struttura dati personalizzata o come println stamperebbe l'oggetto?Implementazione di strutture dati personalizzate mediante protocolli Clojure

Ipotizzando una mappa con due vettori,

{:a [] :b []} 

Quando chiamato prima su di esso desidero prendere dal: un vettore ma quando conj su questa struttura vorrei conj a: b. Posso usare i protocolli per ottenere questo tipo di comportamento?

+0

Non ancora dal momento che quasi nessun fn di basso livello (tranne ridurre) è stato "protocollo" ma è possibile utilizzare defrecord o deftype per definire un tipo di dati che si comporta come si desidera. – cgrand

+0

Sì, ma poi devo implementare le funzioni che agiscono anche su di esso, creando nomi di funzioni ridondanti, come head che funziona come prima cosa per la mia struttura dati, no? –

+0

Se si utilizza deftype, è possibile fornire implementazioni per le varie interfacce utilizzate da Clojure (come clojure.lang.ISeq) che fanno ciò che si desidera. – Brian

risposta

13

Alcune cose sono ancora implementate come interfacce Java in Clojure; di quelli, direi che alcuni rimarranno per sempre in questo modo per facilitare la collaborazione con il codice Clojure da altre lingue JVM.

Fortunatamente, quando si definisce un tipo utilizzando deftype, si può avere il nuovo tipo implementare qualsiasi interfaccia Java richiesta (che Brian ha menzionato in un commento sopra), nonché qualsiasi metodo di java.lang.Object. Un esempio per abbinare il vostro descrizione potrebbe essere simile a questo:

(deftype Foo [a b] 
    clojure.lang.IPersistentCollection 
    (seq [self] (if (seq a) self nil)) 
    (cons [self o] (Foo. a (conj b o))) 
    (empty [self] (Foo. [] [])) 
    (equiv 
    [self o] 
    (if (instance? Foo o) 
    (and (= a (.a o)) 
      (= b (.b o))) 
    false)) 
    clojure.lang.ISeq 
    (first [self] (first a)) 
    (next [self] (next a)) 
    (more [self] (rest a)) 
    Object 
    (toString [self] (str "Foo of a: " a ", b: " b))) 

Un esempio di cosa si può fare con esso al REPL:

user> (.toString (conj (conj (Foo. [] []) 1) 2)) 
"Foo of a: [], b: [1 2]" 
user> (.toString (conj (conj (Foo. [:a :b] [0]) 1) 2)) 
"Foo of a: [:a :b], b: [0 1 2]" 
user> (first (conj (conj (Foo. [:a :b] [0]) 1) 2)) 
:a 
user> (Foo. [1 2 3] [:a :b :c]) 
(1 2 3) 

Si noti che il REPL stampa come un ss; Credo che sia a causa dell'implementazione in linea di clojure.lang.ISeq. È possibile saltarlo e sostituire il metodo seq con uno che restituisce (seq a) per una rappresentazione stampata utilizzando l'toString personalizzato. str utilizza sempre toString, tuttavia.

Se è necessario il comportamento personalizzato delle funzioni familiari pr (incluso println ecc.), Sarà necessario implementare un numero personalizzato print-method per il proprio tipo. print-method è un multimetodo definito in clojure.core; dare un'occhiata a core_print.clj nelle fonti di Clojure per esempio implementazioni.

0

Stavo giocando con le collezioni personalizzate e volevo personalizzare l'output per il REPL, così ho finito per seguire il consiglio di Michal sull'ultimo punto. Ho incluso lo snippet di codice su come farlo perché ho scoperto che passare alla fonte mi ha richiesto un po 'perché non ho trovato questo spiegato altrove.

(defmethod print-method your.custom.collection.goes.Here [c, ^java.io.Writer w] 
    (.write w (str "here is my custom output: " c))) 

Questo è utile, ad esempio, nei casi in cui l'seq è sempre la stampa del vettore personalizzato con parentesi (come con l'esempio di Michal) e si desidera parentesi quadre come normali vettori Clojure:

(defmethod print-method your.custom.Vector [v, ^java.io.Writer w] 
    (.write w (str (into [] v)))) 

Significa anche che ora è possibile implementare seq per restituire effettivamente una sequenza del tipo di dati piuttosto che doverla implementare per l'output REPL.

Problemi correlati