I have a clojure function:
(defn f [arg1 arg2]
...)
I would like to test if arg1
and arg2
are numeric (only numeric types should pass - not numerically formatted strings). There are, of course, a whole bunch of ways to do this, but I'd like to do it as idiomatically as possible. Suggestions?
Edit: I know about :pre
. Any comment on whether or not that is an appropriate/necessary way to handle this would be appreciated.
Pre-conditions can do that:
(defn test [arg1 arg2]
{:pre [(number? arg1) (number? arg2)]}
(+ arg1 arg2))
(test 1 2)
=> 3
(test 1 "2")
=> Assert failed: (number? arg2)
See http://clojure.org/special_forms#toc9 for docs.
The number?
function sounds like what you need. Maybe a test of (and (number? arg1) (number? arg2))
.
A while back, Brian Carper suggested a macro and series of functions to use in validating different types of numeric arguments:
;; 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)))
This has proved very flexible in my experience. As you can see, it is easy to extend the mulitmethod to new tests.
Usage would be something like:
(defn f [arg1 arg2]
"arg1 must be a positive integer, arg2 must be a positive number"
(validate [arg1 :posint] [arg2 :posnum])
...
)
I've invented Dire for such a purpose!
(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")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With