2011-12-22 15 views
8

Ho una funzione clojure:Validazione di argomenti numerici in Clojure

(defn f [arg1 arg2] 
    ...) 

Vorrei verificare se arg1 e arg2 sono numerici (solo i tipi numerici dovrebbero passare - non stringhe formattate numericamente). Ci sono, naturalmente, un sacco di modi per farlo, ma mi piacerebbe farlo nel modo più idiomatico possibile. Suggerimenti?

Modifica: so di :pre. Sarebbe apprezzato qualsiasi commento sull'opportunità o meno di un modo appropriato/necessario per gestirli.

risposta

12

pre-condizioni possono farlo:

(defn test [arg1 arg2] 
    {:pre [(number? arg1) (number? arg2)]} 
    (+ arg1 arg2)) 

(test 1 2) 
=> 3 

(test 1 "2") 
=> Assert failed: (number? arg2) 

Vedi http://clojure.org/special_forms#toc9 per documenti.

+0

Grazie, Justin. Sarebbe una cattiva forma per me non usare ': pre' e usare qualcos'altro invece? Mi capita di trovarmi in una situazione in cui è un po 'difficile (anche se penso che la tua risposta sia probabilmente quella che la maggior parte della gente vorrebbe). – charleslparker

+0

Impossibile esprimere un giudizio senza ulteriori dettagli. Perché sta usando ': pre' difficile? –

4

La funzione number? suona come quello che ti serve. Forse un test di (and (number? arg1) (number? arg2)).

Qualche tempo fa, Brian Carper ha suggerito una serie di macro e di funzioni da utilizzare nel convalidare diversi tipi di argomenti numerici:

;; Suggested by Brian Carper at: 
;;http://stackoverflow.com/questions/1640311/should-i-use-a-function-or-a-macro-to-validate-arguments-in-clojure 

(defmacro assert* [val test] 
    `(let [result# ~test] 
    (when (not result#) 
     (throw (IllegalArgumentException. 
       (str "Test failed: " (quote ~test) 
        " for " (quote ~val) " = " ~val)))))) 

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

(defmethod validate* :prob [x _] 
    (assert* x (and (number? x) (pos? x) (<= x 1.0)))) 

(defmethod validate* :posint [x _] 
    (assert* x (and (integer? x) (pos? x)))) 

(defmethod validate* :non-negint [x _] 
    (assert* x (and (integer? x) (not (neg? x))))) 

(defmethod validate* :posnum [x _] 
    (assert* x (and (number? x) (pos? x)))) 

(defmethod validate* :percentage [x _] 
    (assert* x (and (number? x) (pos? x) (<= x 100)))) 

(defmethod validate* :numseq [x _] 
    (assert* x (and (not (empty? x)) (seq? x) (every? number? x)))) 

(defmethod validate* :nonzero-numseq [x _] 
    (assert* x (and (not (empty? x)) (seq? x) (every? #(and (number? %) (not (zero? %))) x)))) 

(defmethod validate* :posint-seq [x _] 
    (assert* x (and (not (empty? x)) (seq? x) (every? #(and (integer? %) (pos? %)) x)))) 

(defmethod validate* :prob-seq [x _] 
    (assert* x (and (not (empty? x)) (seq? x) (every? #(and (number? %) (pos? %) (<= % 1.0)) x)))) 

(defmethod validate* :default [x _] 
    (throw (IllegalArgumentException. 
       (str "Unrecognized validation type")))) 

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

Questo si è dimostrato molto flessibile nella mia esperienza. Come puoi vedere, è facile estendere il mulitmetodo a nuovi test.

Uso sarebbe qualcosa di simile:

(defn f [arg1 arg2] 
    "arg1 must be a positive integer, arg2 must be a positive number" 
    (validate [arg1 :posint] [arg2 :posnum]) 
    ... 
) 
+0

Grazie - sì, questo mi sembra quello di cui ho bisogno. Che dire ': pre'? Pensi che sia giusto non usarlo qui? – charleslparker

+0

@mistertero: ': pre' non esisteva quando ho posto la domanda originale (Clojure pre-1.0) e mi sono abituato alla macro. Penso che l'utilizzo della macro possa portare a una minore digitazione; immagina di dover digitare la condizione per ': nonzero-numseq' per esempio. ': pre' è probabilmente migliore per condizioni una tantum, molto specifiche come test per valori specifici piuttosto che intere classi di numeri. – clartaq

-1
(defn add [^:number a ^:number b] (+ a b)) 
1

Ho inventato Dire per tale scopo!

(defn f [a b] 
    (+ a b)) 

(defprecondition f 
    :args-numeric 
    (fn [a b & more] 
    (and (number? a) (number? b)))) 

(defhandler f 
    {:precondition :args-numeric} 
    (fn [e & args] (apply str "Failure for argument list: " (vector args)))) 

(supervise f "illegal" "args") 
0

ho trovato this durante la ricerca la stessa cosa:

(defmacro verify-my-arg 
"Like assert, except for the following differences: 
1. does not check for *assert* flag 
2. throws IllegalArgumentException" 
[err-msg arg] 
`(if ~arg true 
    (throw (IllegalArgumentException. ~err-msg)))) 

e quindi si può utilizzare così:

(defn foo [m] 
{:pre [(verify-my-arg "m must be a map" (map? m))]} 
(println m)) 

Spero che aiuta! (Tutti i crediti vanno alla risposta originale di Shantanu Kumar nei gruppi di google)

Problemi correlati