8

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.

charleslparker
  • 1,904
  • 1
  • 21
  • 31

5 Answers5

13

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.

Justin Kramer
  • 3,983
  • 1
  • 23
  • 23
  • Thanks, Justin. Would it be bad form for me to not use `:pre` and use something else instead? I happen to be in a situation where it's a bit difficult (although I think your answer is probably the one most people will want). – charleslparker Dec 22 '11 at 17:07
  • Can't make a judgment without more details. Why is using `:pre` difficult? – Justin Kramer Dec 22 '11 at 18:47
5

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])
  ...
)
clartaq
  • 5,320
  • 3
  • 39
  • 49
  • Thanks - yes, this seems like what I need. What about `:pre`? Do you think it is okay not to use that here? – charleslparker Dec 22 '11 at 17:02
  • @mistertero: `:pre` didn't exist back when I asked the original question (Clojure pre-1.0) and I've gotten used to the macro. I think using the macro can lead to less typing; imagine having to type the condition for `:nonzero-numseq` for example. `:pre` is probably better for one-off, very specific conditions like testing for specific values rather than entire classes of numbers. – clartaq Dec 22 '11 at 18:12
1

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")
Mike
  • 19,267
  • 11
  • 56
  • 72
0

i've found this while searching the same thing:

(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))))

and then you can use it like that:

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

I hope it helps! (All credits go to the original answer of Shantanu Kumar in google groups)

danfromisrael
  • 2,982
  • 3
  • 30
  • 40
-1
(defn add [^:number a ^:number b] (+ a b))
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70