我应该使用函数或宏来验证Clojure中的参数吗?

时间:2009-10-28 21:42:44

标签: validation macros clojure

我在Clojure中有一组数字函数,我想验证参数。函数预期有许多类型的参数,例如正整数,百分比,数字序列,非零数字序列等。我可以通过以下方式验证任何单个函数的参数:

  1. 直接编写验证码 功能。
  2. 写一个将军 目的函数,传递它 参数和预期类型。
  3. 编写通用宏, 传递参数和 期望的类型。
  4. 我没有想过的其他人。
  5. Larry Hunter的一些Lisp code是#3的一个很好的例子。 (查找test-variables宏。)

    我的直觉是宏更合适,因为对评估的控制和编译时计算的可能性,而不是在运行时完成所有操作。但是,我没有遇到我正在编写的代码似乎需要它的用例。我想知道编写这样一个宏是否值得付出努力。

    有什么建议吗?

3 个答案:

答案 0 :(得分:13)

Clojure已经对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]
但是有点难看。

我可能会编写一个宏,因此我可以简洁地报告哪些测试失败(引用并按字面打印测试)。您链接到的CL代码看起来非常讨厌这个巨大的案例陈述。在我看来,Multimethods在这里会更好。你可以很容易地把这样的东西扔到一起。

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

然后:

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

答案 1 :(得分:3)

只是一些想法。

我有一种感觉,这取决于验证的复杂性和数量,以及功能的性质。

如果您正在进行非常复杂的验证,则应该将验证器从功能中分离出来。原因是您可以使用更简单的方法来构建更复杂的方法。

例如,你写道:

  1. 验证器,以确保列表不为空,
  2. 验证器,以确保值大于零,
  3. 使用1和2确保值是非空的大于零的值列表。
  4. 如果您只是进行了大量的简单验证,并且您的问题是冗长的,(例如,您有50个函数都需要非零整数),那么宏可能更有意义。

    要考虑的另一件事是功能评估是Clojure渴望的。如果您知道函数将失败,或者根据其他参数的值不需要某些参数,则在某些情况下可以通过不评估某些参数来获得性能提升。例如。每一个?谓词不需要评估集合中的每个值。

    最后,要解决“你没有想过的其他人”。 Clojure支持基于调度功能的通用调度。该函数可以根据任意数量的因素调度到适当的代码或错误消息。

答案 2 :(得分:1)

如果您想要修改语言以自动将测试添加到块中定义的任何函数,需要宏的情况如下:

(with-function-validators [test1 test2 test4]  
    (defn fun1 [arg1 arg2] 
        (do-stuff))
    (defn fun2 [arg1 arg2] 
        (do-stuff))
    (defn fun3 [arg1 arg2] 
        (do-stuff)))