Clojure中数字参数的验证

8

我有一个Clojure函数:

(defn f [arg1 arg2]
  ...)

我想测试arg1arg2是否为数字(只有数字类型可以通过-不是数字格式化的字符串)。当然,有很多方法可以做到这一点,但我想尽可能地达到惯用语的效果。有什么建议吗?
编辑:我知道:pre。任何关于是否适当/必要处理此操作的评论将不胜感激。
5个回答

13

预先条件可以做到这一点:

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

(test 1 2)
=> 3

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

请查看http://clojure.org/special_forms#toc9了解更多信息。


谢谢,Justin。如果我不使用:pre而使用其他东西,这会是不好的形式吗?我恰好处于一个有点困难的情况下(尽管我认为你的答案可能是大多数人想要的)。 - charleslparker
没有更多的细节,无法做出判断。为什么使用 :pre 很困难? - Justin Kramer

5

你可能需要使用number?函数。也许可以测试一下(and (number? arg1) (number? arg2))

不久前,Brian Carper建议使用宏和一系列函数来验证不同类型的数字参数:

;; Suggested by Brian Carper at:
;;https://dev59.com/9XI-5IYBdhLWcg3w3cc8

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

这在我的经验中非常灵活。如您所见,很容易将mulitmethod扩展到新测试。
使用方法类似于:
(defn f [arg1 arg2]
  "arg1 must be a positive integer, arg2 must be a positive number"
  (validate [arg1 :posint] [arg2 :posnum])
  ...
)

谢谢 - 是的,这似乎是我需要的。那么 :pre 呢?你觉得在这里不使用它是否可以? - charleslparker
@mistertero:当我提出原始问题(Clojure pre-1.0)时,:pre还不存在,而我已经习惯了使用宏。我认为使用宏可以减少打字量;例如,想象一下必须为:nonzero-numseq键入条件。 :pre可能更适合一次性、非常特定的条件,例如测试特定值而不是整个数字类别。 - clartaq

1

我已经为这个目的发明了Dire

(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

我在搜索同样的东西时发现了this:

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

然后你可以像这样使用它:

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

希望这对你有所帮助!(所有功劳归功于谷歌小组中Shantanu Kumar的原始答案)


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接