2010-07-21 17 views
16

Ho una complessa struttura dati Clojure che vorrei serializzare, in pratica l'intero stato di gioco corrente per un gioco online che sto sviluppando, in modo da poter implementare i file di salvataggio.Serializzazione della struttura dati Clojure

miei requisiti sono:

  • Una qualche forma di formato di testo leggibile (probabilmente preferirei s-espressioni, JSON e XML in questo ordine, ma aperta agli altri)
  • Support tutto il solito strutture di dati Clojure, parole chiave e primitive
  • capacità di fornire funzioni di serializzazione/deserializzazione su misura per le classi Java personalizzato, defrecords ecc (questo è importante perché ho bisogno di fare qualcosa di simile readResolve di Java in diversi casi)
  • Buona prestazione è un ice-to-have

Qualche consiglio?

+0

Senza sapere molto su Clojure, c'è una ragione per cui questo non è realizzabile utilizzando i meccanismi di serializzazione Java standard richiamati da Clojure? – Gian

+0

@Gian - sì è certamente possibile, ma sto cercando di imparare il "modo Clojure" di fare le cose :-) – mikera

+0

IMO il modo Clojure è usare le strutture di Java dove forniscono buone soluzioni ai problemi che affrontano. :-) 'Serializable' può essere una buona soluzione per la memorizzazione/trasferimento a breve termine di strutture di dati. Detto questo, suppongo che per questo caso d'uso sia necessario un formato più adatto alla memorizzazione a lungo termine e questo potrebbe essere fornito da 'print-dup'. ('Serializable' potrebbe incorrere in problemi se, per esempio, la struttura delle classi implementa le modifiche delle strutture dati core Clojure:' print-dup' probabilmente no.) –

risposta

11

Se si voleva serializzare cose da S-espressioni, è possibile utilizzare print-dup:

(binding [*print-dup* true] (println [1 2 3])) 
; prints [1 2 3] 

(defrecord Foo [x]) 
; => user.Foo 
(binding [*print-dup* true] (println (Foo. :foo))) 
; prints #=(user.Foo/create {:x :foo}) 

Si noti che la stampa di una struttura che contiene, per esempio, dieci i riferimenti a un singolo vettore, seguita dalla lettura indietro ti dà una infrastruttura con dieci separati (non identical?), sebbene equivalenti in termini di struttura (=) vettori.

Per utilizzare questo nei casi in cui non è prevista l'implementazione predefinita, implementare il multimetodo clojure.core/print-dup.

Inoltre, un sacco di cose in Clojure 1.2 sono java.io.Serializable:

(every? (partial instance? java.io.Serializable) 
     [{1 2} #{"asdf"} :foo 'foo (fn [] :foo)]) 
; => true 

(defrecord Foo []) 
(instance? java.io.Serializable (Foo.)) 
; => true 

noti che si dovrebbe evitare di serializzazione di runtime-creato fn s - sono istanze di classi one-off con nomi strani e hai vinto essere in grado di deserializzarli dopo aver riavviato la JVM comunque. Con la compilazione AOT, fn s ottengono i propri nomi di classe fissi.

Aggiornamento: Come menzionato in un commento sulla questione, Serializable è più adatto allo stoccaggio a breve termine/trasferimento dei dati, mentre print-dup dovrebbe essere più robusto come una soluzione di storage a lungo termine (che lavora in molte versioni di l'applicazione, Clojure ecc.). Il motivo è che print-dup non dipende in alcun modo dalla struttura delle classi serializzate (quindi un vettore print-dup oggi sarà ancora leggibile quando l'implementazione del vettore passa da Java a Clojure deftype).

+0

Penso che 'print-dup' potrebbe essere la soluzione migliore per questo caso d'uso, vedere il mio commento sulla domanda ... Se i giochi di salvataggio possono diventare grandi, le stampe possono sempre essere compresse. –

3

per JSON è possibile utilizzare lo standard clojure-contrib.json. Anche se, come ricordo, tutti gli oggetti Clojure dovrebbero essere serializzabili ...

+0

Ho provato che non mi piacciono le mie classi Java: getta ad es java.lang.Exception: Non so come scrivere JSON della classe mikera.persistent.SparseMap. C'è un modo per dargli funzioni di serializzazione personalizzate per le tue classi? – mikera

+0

Si potrebbe fare in modo che la classe implementa l'interfaccia 'clojure.contrib.json.Write_JSON' (o il protocollo' clojure.contrib.json/Write-JSON', se la classe è un tipo/record Clojure). Non sono sicuro di come faresti a leggerli di nuovo, però; Penso che per strutture dati non standard vorresti utilizzare qualcosa come YAML. Una rapida ricerca su Google trova 'clj-yaml' http://github.com/lancepantz/clj-yaml come una possibile soluzione, anche se in realtà non so nulla del progetto. –

+0

Ah, ho appena notato che README di 'clj-yaml' dice che supporta solo la deserializzazione per ora ... –

4

Se tutto è una struttura dati Clojure, allora è già serializzato (codice b/c del codice < -> dati). Basta scaricare le strutture dati sul disco. Per ripristinare, caricarli di nuovo e (eval).

+0

Come si esegue il dump su disco? – Zubair

+0

Collega '* out *' a un flusso di file e usa '(pr)' (e poi usa '(load-file)' per leggerlo di nuovo). Vedi http://groups.google.com/group/clojure/browse_thread/thread/cb5246d07142a3dc?fwc=2&pli=1 per un esempio funzionante. – Greg

+1

Non li valuta pericolosi? Qualcuno potrebbe introdurre del codice. Dovrebbero essere letti ma non valutati, credo. – Pablo

6

edn-format è stato rilasciato come standard per il trasferimento dei dati utilizzando le strutture dati di Clojure.

È una soluzione particolarmente adatta per la serializzazione di strutture/valori di dati Clojure ed è supportata in più lingue, pertanto può anche essere utilizzato come formato di scambio di dati.

+0

funziona bene per le strutture dati Clojure principali, ma diciamo che hai una struttura di dati più esotica, come una PriorityMap (che ha l'aspetto di una PersistentMap ma si comporta diversamente), quindi dovresti usare i macro del lettore o qualcosa del genere? – Hendekagon

+0

Hanno però questo in formato edn: puoi "taggare" valori per indicare che sono di un tipo speciale e scrivere i tuoi gestori per costruire la classe appropriata. È carino celever! – mikera

Problemi correlati