2011-10-07 13 views
9

Ciao ragazzi: trovo che le mie app per clojure vengano strutturalmente accoppiate molto rapidamente a causa della mancanza di un'API di dati ...- Ho mappe con chiavi che hanno nomi che, se digitati in modo errato, causare eccezioni o errori. Ho anche notato che è facile commettere errori durante la destrutturazione di una lista (ad esempio, forse distruggete la parte sbagliata della lista) .....Come costruire robusti apis di dati nel clojure

Provenendo dal mondo java, normalmente utilizzo il mio IDE per aiutarmi ad ottenere i dati "giusti" al di fuori degli oggetti dati minimali e non ordinati --- ma il passaggio delle mappe clouche sembra essere il paradigma opposto a questo.

In che modo i clojuriani codificano in modo difensivo in assenza di un sistema di tipi o di completamento del codice ide ...?

risposta

5

Scrivere le funzioni dei validatori per i tuoi "schemi" (chiavi ma anche tipo di valori, ecc.) Quindi utilizzare le condizioni pre e post nel codice - poiché la loro sintassi è poco nota ecco un rapido aggiornamento:

(defn foo [x y] ; works with fn too 
    {:pre [(number? x) (number? y)] 
    :post [(number? %) (pos? %)]} 
    (+ (* x x) (* y y))) 

Si basano su assert e quindi possono essere disabilitati. (doc assert) per maggiori dettagli.

+0

Dato che questo è stato pubblicato, core.typed è stato rilasciato e può anche fare la stessa cosa http://typedclojure.org/ – Ben

5

Forse stai cercando i documenti?

(require '[clojure.set :as cset]) 

(defrecord Person [name age address phone email]) 

    ;; Make a keyword-based constructor to verify 
    ;; args and decouple ordering. 
(let [valid #{:name :age :address :phone :email}] 
    (defn mk-person[& args] 
    (let [h (apply hash-map args) 
      invalid (cset/difference (set (keys h)) valid)]  
     (when-not (empty? invalid) 
     (throw (IllegalArgumentException. (pr-str invalid)))) 
     ; any other argument validation you want here 
     (Person. 
     (:name h) (:age h) (:address h) (:phone h) (:email h))))) 

=> (def p (mk-person :name "John" :email "[email protected]")) 
#:user.Person{:name "John", :age nil, :address nil, :phone nil, 
       :email "[email protected]"} 

Ora è possibile scegliere se si desidera eccezioni per i nomi di errori di battitura accedendo ai dati con le funzioni (eccezione) o parole chiave (non eccezione).

=> (.fax p) 
java.lang.IllegalArgumentException: 
    No matching field found: fax for class user.Person 
=> (:fax p) 
nil 

Questo approccio richiede che vengano evitati i nomi di campo che sarebbero in conflitto con i metodi esistenti. (Vedi commento da @Jouni.)

In alternativa, è possibile ignorare la limitazione nome del campo utilizzando le parole chiave per la ricerca e una funzione di accesso che verifica la presenza di chiavi non valide:

(defn get-value [k rec] 
    (let [v (k rec ::not-found)] 
    (if (= v ::not-found) 
     (throw (IllegalArgumentException. (pr-str k))) 
    v))) 

=> (get-value :name p) 
"John" 
=> (get-value :fax p) 
IllegalArgumentException: :fax 

"destrutturazione del torto parte della lista "problemi di tipo possono venire dal tentativo di codificare qualcosa come" persona "in una lista; quindi devi ricordare cose come "il codice postale è il quarto elemento nell'elenco" indirizzo "nella terza posizione nell'elenco" Persona ".

In Lisp "classico" si potrebbe risolvere scrivendo le funzioni di accesso, in Clojure è possibile utilizzare i record.

I tipi causano problemi in qualsiasi linguaggio di programmazione, il meglio che puoi fare è cercare di catturarli prima.

Un IDE Java con completamento automatico potrebbe rilevare alcuni refusi durante la digitazione e un linguaggio tipizzato in modo statico ne catturerà molti in fase di compilazione, ma in un linguaggio dinamico non li troverete fino al runtime. Alcune persone considerano questo un inconveniente dei linguaggi dinamici (inclusi Python, Ruby ecc.), Ma data la loro popolarità, alcuni programmatori ritengono che la flessibilità ottenuta e il codice salvato siano più importanti della perdita di errori di completamento automatico e compilazione di IDE.

Il principio è lo stesso in entrambi i casi: le eccezioni precedenti sono migliori, poiché c'è meno codice da attraversare per trovare la causa. Idealmente la traccia dello stack ti porterebbe direttamente all'errore di battitura. In Clojure, le funzioni di registrazione e accesso ti danno questo.

+1

Non esiste tuttavia un campo di record denominato 'size'. Le chiamate a '(.size rec)' chiamano il * metodo * 'size' definito in' java.util.Collection', e allo stesso modo per tutti i metodi null in tutte le interfacce che hanno i record Clojure. –

+0

@Jouni Sì, ci sono 17 nomi che non è possibile utilizzare; 'size',' count', 'values' e' meta' sono probabilmente i più problematici. Dovresti trattarli come parole riservate ed evitare di usarli o scrivere la tua API di dati. PER QUANTO NE SO. –