2011-10-26 16 views
7

Ci sembrano essere molteplici modi per implementare modelli di dati in Clojure:L'implementazione di un modello di dati per evitare gli errori più comuni

  • ordinarie tipi built-in (mappe/lists/insiemi/vettori)
  • incorporato tipi di dati + meta-dati - ad esempio: (type ^{:type ::mytype} {:fieldname 1})
  • tipi built-in + funzioni speciali accessor (per esempio, get ting una chiave inesistente da una mappa genera un'eccezione, invece di tornare in silenzio nil)
  • DEFTYPE
  • defstruct
  • defrecord
  • defprotocol

Abbiamo raggiunto il punto in cui le mappe/elenchi non funziona più bene per noi sono - ci si imbatte in un sacco di errori che pre-condizioni/post- le condizioni potrebbero facilmente catturare, ma impiegare molto tempo per cercare altrimenti (ed è difficile scrivere condizioni pre/post efficaci per le funzioni che accettano mappe/elenchi/vettori annidati), ma non siamo sicuri di quale dei precedenti scegliere da.

Abbiamo tre obiettivi principali:

  • scrivere codice Clojure idiomatica
  • evitare di spendere grandi quantità di tempo a caccia di errori di tipo stupidi
  • hanno fiducia nella nostra capacità di cambiare/code refactoring con silenziosamente rompere nulla

Come possiamo sfruttare la potenza del Clojure a h scaldarci?

risposta

4

cultura Clojure è fortemente a favore dei tipi di dati grezzi. Giustamente. Ma i tipi espliciti possono essere utili. Quando i tuoi semplici tipi di dati diventano sufficientemente complessi e specifici, hai essenzialmente un dataype implicito senza le specifiche.

Affidatevi a costruttori. Sembra un po 'sporco, in un modo OOP, ma un costruttore non è altro che una funzione che crea il tipo di dati in modo sicuro e conveniente. Uno svantaggio di semplici strutture di dati è che incoraggiano la creazione dei dati al volo. Quindi, invece di chiamare (myconstructor ...), cerco di comporre i miei dati direttamente. E con molte possibilità di errore, oltre a problemi se ho bisogno di cambiare il tipo di dati sottostante.

Le registrazioni sono il punto ideale. Con tutto il trambusto sui tipi di dati non elaborati, è facile dimenticare che i record fanno un sacco di cose che le mappe possono fare. Possono essere consultati allo stesso modo. Puoi chiamare seq su di loro. Puoi distruggerli allo stesso modo. La stragrande maggioranza delle funzioni che si aspettano una mappa accetterà anche un record.

I metadati non ti salveranno. La mia principale obiezione a fare affidamento sui metadati è che non si riflette nell'eguaglianza.

user> (= (with-meta [1 2 3] {:type :A}) (with-meta [1 2 3] {:type :B})) 
true 

Se questo è accettabile o meno dipende da voi, ma mi piacerebbe preoccuparsi di questo che introducono nuovi bug sottili.


Le altre opzioni dataype:

  • DEFTYPE è solo per il lavoro di basso livello nella creazione di nuove strutture di dati finalità di base o speciali. Diversamente dalla defrecord, non porta con sé tutta la bontà del clojure. Per la maggior parte del lavoro, non è necessario o consigliabile.
  • defstruct dovrebbe essere deprecato. Quando Rich Hickey ha introdotto tipi e protocolli, ha sostanzialmente affermato che la decortazione deve essere sempre preferita.

protocolli sono molto utili, anche se si sentono come un po 'di una partenza dalla (funzioni + dati) paradigma. Se ti ritrovi a creare record, dovresti prendere in considerazione la definizione di protocolli.

EDIT: ho scoperto un altro vantaggio per i tipi di dati semplici, che non erano stati evidente per me prima: se si sta facendo la programmazione web, i tipi di dati semplici possono essere convertiti da e per JSON in modo efficiente e facilmente. (Le librerie per fare questo includono clojure.data.json, clj-json e il mio preferito, cheshire). Con record e tipi di dati, il compito è notevolmente più fastidioso.

+0

Ok, quindi ho bisogno di capire 'defrecord' e' defprotocol', posso ignorare 'defstruct', e non devo preoccuparmi troppo di' deftype'. È importante per un programma clojure, che 'defrecord' crei un codice java - nel senso che non voglio preoccuparmi di avere una classe java, ma se il clojure vuole usarne uno in privato, va bene? Ottima risposta, molto hepful. –

+0

Bene, una mappa semplice è una classe java, come puoi vedere su https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/APersistentMap.java –

+0

Quindi non è proprio un enorme problema che defrecord crea una classe java. Dal nostro punto di vista, usando il disco, non farà molta differenza - tranne che per la costruzione, si sentirà del tutto simile ai dati del clojure. –

1

È davvero comodo poter comporre funzioni che funzionano su mappe ed elenchi, e sarebbe un peccato ignorarlo passando a classi e protocolli. dopotutto è meglio avere cento funzioni su un tipo. Passare a protocolli o record sarebbe un po 'pesante. Ti impedirebbe di effettuare il debug da (debug (map :rows (get-state)) ad esempio.

I metadati sono un ottimo modo per aggiungere "quel tanto che basta" per rendere i tuoi dati più sicuri nei luoghi che ne hanno bisogno senza perdere i benefici nel resto della tua base di codice. Consiglierei andando con l'opzione 2

  • 'tipi di dati built-in + meta-dati ((tipo^{: Tipo :: MyType} {: fieldname 1}))'
+0

Non è un Alan Perlis-ism circa cento funzioni su un tipo di dati? Ho riformattato quell'esempio nel titolo, inserendolo accidentalmente in parentesi non LISP! –

+0

sì, questo è il preventivo che volevo, credito in cui è dovuto il credito! –

Problemi correlati