Domanda

Ho un gruppo di funzioni numeriche in Clojure per cui voglio validare gli argomenti. Esistono numerosi tipi di argomenti attesi dalle funzioni, come numeri interi positivi, percentuali, sequenze di numeri, sequenze di numeri diversi da zero e così via. Posso convalidare gli argomenti per qualsiasi singola funzione tramite:

  1. Scrittura del codice di validazione direttamente in la funzione.
  2. Scrivere un generale funzione scopo, passando a argomenti e tipi previsti.
  3. Scrivere una macro generica, passando gli argomenti e tipi previsti.
  4. Altri a cui non ho pensato.

Alcuni Codice Lisp di Larry Hunter è un bell'esempio di # 3. (Cerca la macro variabili di test .)

La mia intuizione è che una macro è più appropriata a causa del controllo sulla valutazione e del potenziale per eseguire calcoli in fase di compilazione piuttosto che eseguirli tutti in fase di esecuzione. Ma non ho incontrato un caso d'uso per il codice che sto scrivendo che sembra richiederlo. Mi chiedo se valga la pena scrivere questa macro.

Qualche suggerimento?

È stato utile?

Soluzione

Clojure ha già (non documentato, forse soggetto a modifiche) supporto per condizioni pre e post su fn s.

user> (defn divide [x y]
        {:pre [(not= y 0)]}
        (/ x y))
user> (divide 1 0)
Assert failed: (not= y 0)
   [Thrown class java.lang.Exception]

Un po 'brutto però.

Probabilmente scrivo una macro solo per poter segnalare quali test hanno fallito in modo sintetico (citare e stampare il test letteralmente). Il codice CL a cui ti sei collegato sembra piuttosto brutto con quell'enorme affermazione sul caso. Secondo me, i metodi multipli sarebbero migliori qui. Puoi mettere insieme qualcosa del genere abbastanza facilmente da solo.

(defmacro assert* [val test]
  `(let [result# ~test]              ;; SO`s syntax-highlighting is terrible
     (when (not result#)
       (throw (Exception.
               (str "Test failed: " (quote ~test)
                    " for " (quote ~val) " = " ~val))))))

(defmulti validate* (fn [val test] test))

(defmethod validate* :non-zero [x _]
  (assert* x (not= x 0)))

(defmethod validate* :even [x _]
  (assert* x (even? x)))

(defn validate [& tests]
  (doseq [test tests] (apply validate* test)))

(defn divide [x y]
  (validate [y :non-zero] [x :even])
  (/ x y))

Quindi:

user> (divide 1 0)
; Evaluation aborted.
; Test failed: (not= x 0) for x = 0
;   [Thrown class java.lang.Exception]

user> (divide 5 1)
; Evaluation aborted.
; Test failed: (even? x) for x = 5
;   [Thrown class java.lang.Exception]

user> (divide 6 2)
3

Altri suggerimenti

Solo alcuni pensieri.

Sento che dipende dalla complessità e dal numero di convalide e dalla natura delle funzioni.

Se stai eseguendo validazioni molto complesse, dovresti rompere i tuoi validatori dalle tue funzioni. Il ragionamento è che puoi usarne di più semplici per costruirne di più complessi.

Ad esempio, scrivi:

  1. un validatore per assicurarsi che un elenco non sia vuoto,
  2. un validatore per assicurarsi che un valore sia maggiore di zero,
  3. usa 1 e 2 per assicurarti che un valore sia un elenco non vuoto di valori maggiori di zero.

Se stai semplicemente eseguendo un'enorme quantità di semplici convalide e il tuo problema è la verbosità, (ad esempio hai 50 funzioni che richiedono tutte numeri interi diversi da zero), allora una macro probabilmente ha più senso.

Un'altra cosa da considerare è che la valutazione della funzione è Clojure è desideroso. In alcuni casi potresti ottenere un aumento delle prestazioni non valutando alcuni parametri se sai che la funzione fallirà o se alcuni parametri non sono necessari in base ai valori di altri parametri. Per esempio. il ogni? il predicato non ha bisogno di valutare ogni valore in una raccolta.

Infine, per affrontare " altri a cui non hai pensato " ;. Clojure supporta un dispacciamento generico basato su una funzione di invio. Tale funzione potrebbe essere inviata al codice appropriato o ai messaggi di errore in base a un numero qualsiasi di fattori.

Un caso in cui hai bisogno di una macro sarebbe se volessi modificare la lingua per aggiungere automaticamente i test a qualsiasi funzione definita all'interno di un blocco, come questo:

(with-function-validators [test1 test2 test4]  
    (defn fun1 [arg1 arg2] 
        (do-stuff))
    (defn fun2 [arg1 arg2] 
        (do-stuff))
    (defn fun3 [arg1 arg2] 
        (do-stuff)))  
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top