2012-04-04 15 views
5

Ho un grafico ciclico che ho creato utilizzando dosync e ref-set. Quando passo questo a println ottengo un java.lang.StackOverflowError come mi aspetterei, perché è effettivamente cercando di stampare una struttura infinitamente nidificata.Come sovrascrivere il comportamento di println per i tipi di riferimento

ho scoperto che se lo faccio (str my-ref) crea qualcosa che assomiglia [email protected] e in realtà non tentare di attraversare la struttura e stampare tutto fuori, quindi questo risolve il problema nel senso immediato, ma aiuta solo quando sono molto attento a quello che sto stampando sullo schermo. Mi piacerebbe essere in grado di chiamare (println my-graph) come stampare il ref come un tipo di testo personalizzato (che potrebbe coinvolgere lo str) e gli altri elementi non di riferimento normalmente.

Attualmente ho una funzione di stampa personalizzata che stampa ogni elemento della struttura da solo e ignora completamente la stampa di ref. (Si scopre che guardare a [email protected] in realtà non è molto utile). Questo è scomodo da usare e ostacola enormemente l'ispezione casuale di cose al REPL e impedisce anche all'ispettore Emacs di guardare cose mentre sono in una cosa di debug swank.core/break.

Un dettaglio è il ref in realtà è un valore in un defstruct che contiene anche alcune altre cose che sto cercando di stampare normalmente.

Quindi mi chiedo quale strada dovrei andare giù. Vedo queste opzioni:

  1. Capire extend-type e applicare il protocollo CharSequence alla mia struttura ed defstruct in modo che quando si incontra un ref funziona correttamente. Ciò richiede ancora un'ispezione field-by-field della struttura e un caso speciale quando si tratta di ref, ma almeno localizza il problema nella struct e non in tutto ciò che contiene la struct.
  2. Capire come ignorare il protocollo CharSequence quando si incontra uno ref. Ciò consente un comportamento ancora più localizzato e mi consente di visualizzare un riferimento ciclico al REPL anche quando non si trova all'interno di una struttura. Questa è la mia opzione preferita.
  3. Capire come fare qualcosa con toString che credo sia chiamato ad un certo livello quando faccio println. Sono molto ignorante su questa opzione. Abbastanza ignorante anche sugli altri, ma ho letto Joy of Clojure e ora sono tutto ispirato.

Allo stesso modo questa soluzione dovrebbe valere per print e pprint e qualsiasi altra cosa che normalmente barf quando si tenta di stampare un ref ciclico. Quale strategia dovrei assumere?

grazie mille per qualsiasi input.

+1

fyi, l'output di '' (str my-ref) '' è quasi certamente il risultato della chiamata di '' java.lang.Object # toString() '' come descritto qui: http://docs.oracle. com/javase/7/docs/api/java/lang/Object.html # toString% 28% 29 – sw1nn

+0

Si noti che 'defstruct' è stato sostituito da' defrecord'. Inoltre, 'defstruct' non crea un tipo reale, quindi non può partecipare ai protocolli. – raek

+0

Mi rendo conto ora che stavo già usando 'defrecord'. Non sono sicuro del motivo per cui il mio post originale diceva "defstruct". – Sonicsmooth

risposta

4

Quello che vuoi fare è creare un nuovo spazio dei nomi e definire le tue funzioni di stampa che modellano il modo in cui il clojure stampa gli oggetti e le impostazioni predefinite sui metodi di clojure.

In dettaglio:

    Creare un ns escluso pr-str e metodi di stampa. Creare un metodo di stampa multimodale (copiare esattamente ciò che è in clojure.core) Creare un metodo predefinito che semplicemente deleghi a clojure.core/print-method Creare un metodo per clojure.lang.Rif. Che non stampa ricorsivamente tutto

Come bonus, se si utilizza il clojure 1.4.0, è possibile utilizzare i letterali con tag in modo che anche la lettura nell'output sia possibile. Dovresti eseguire l'override della mappa *data-readers* per un tag personalizzato e una funzione che restituisce un oggetto ref. Avresti anche bisogno di sovrascrivere il comportamento della stringa di lettura per garantire che venga chiamato il binding per *data-readers*.

ho fornito un esempio per java.io.File

(ns my.print 
    (:refer-clojure :exclude [pr-str read-string print-method])) 

(defmulti print-method (fn [x writer] 
     (class x))) 

(defmethod print-method :default [o ^java.io.Writer w] 
     (clojure.core/print-method o w)) 

(defmethod print-method java.io.File [o ^java.io.Writer w] 
     (.write w "#myprint/file \"") 
     (.write w (str o)) 
     (.write w "\"")) 

(defn pr-str [obj] 
    (let [s (java.io.StringWriter.)] 
    (print-method obj s) 
    (str s))) 

(defonce reader-map 
    (ref {'myprint/file (fn [arg] 
       (java.io.File. arg))})) 

(defmacro defdata-reader [sym args & body] 
    `(dosync 
    (alter reader-map assoc '~sym (fn ~args [email protected])))) 

(defn read-string [s] 
    (binding [*data-readers* @reader-map] 
    (clojure.core/read-string s))) 

Ora si può chiamare in questo modo (println (my.print/pr-str circular-data-structure))

+0

Grazie, sono stato in grado di ottenere questo implementato tranne che ho ottenuto "Impossibile risolvere var: \ * data-readers \ *". Dopo averlo commentato, mi rimane ancora da dover chiamare una funzione speciale quando so che sto stampando un riferimento circolare. Speravo che il clojure stampasse automagicamente il mio riferimento nel modo in cui è possibile in Python sovrascrivendo le funzioni '__str__' o' __repr__'. – Sonicsmooth

+0

'* data-readers *' funziona solo se usi clojure 1.4.0, assicurati anche di accettare la risposta se ti ha aiutato. Puoi anche definire clojure.core/print-methods per i riferimenti, ma poi stai modificando il metodo di stampa globale, che può essere o non essere quello che vuoi. – bmillare

+0

Vedo ... Non ho ancora provato la versione 1.4.0. Tra questa risposta e questo link: http://groups.google.com/group/clojure/browse_thread/thread/ef2834ec5937baec/1dba8d1753cda39c sono stato in grado di risolvere il mio problema. Grazie. – Sonicsmooth

4

ho trovato la mia soluzione - basta creare un multimethod sovraccaricando clojure.core/print-method per ogni particolare digita e chiama la funzione generica all'interno del multimetodo. In questo caso, ogni multimetodo chiama il generico print-graph-element che sa cosa fare con i riferimenti (stampa semplicemente l'hash dell'oggetto di riferimento anziché provare a stampare il valore dell'oggetto di riferimento). In questo caso suppongo che la funzione di invio del metodo di stampa sia solo (type <thing>), quindi invia il tipo, ed è così che sto scrivendo il multimetodo. In realtà non sono riuscito a trovare alcuna documentazione sul modo in cui funziona la spedizione del metodo di stampa, ma sicuramente sembra comportarsi in questo modo.

Questa soluzione mi permette di digitare solo il nome dell'oggetto, come ad esempio v0 (che è stato cerated da (make-vertice) nel repl e metodi di stampa viene chiamato dal repl, impedendo così il mio errore StackOverflow.

(defmethod clojure.core/print-method vertex [v writer] 
    (print-graph-element v)) 
(defmethod clojure.core/print-method edge [e writer] 
    (print-graph-element e)) 
1

Se si desidera solo essere in grado di stampare strutture dati con i loop, provare a impostare *print-level* di limitare quanto in profondità la stampante scenderà.

user=> (def a (atom nil)) 
#'user/a 
user=> (set! *print-level* 3) 
3 
user=> (reset! a a) 
#<[email protected]: #<[email protected]: #<[email protected]: #>>> 

C'è anche una var *print-length* che può essere utile quando tu si occupano di dati con lunghezza infinita.