我在Clojure中有一组数字函数,我想验证参数。函数预期有许多类型的参数,例如正整数,百分比,数字序列,非零数字序列等。我可以通过以下方式验证任何单个函数的参数:
Larry Hunter的一些Lisp code是#3的一个很好的例子。 (查找test-variables
宏。)
我的直觉是宏更合适,因为对评估的控制和编译时计算的可能性,而不是在运行时完成所有操作。但是,我没有遇到我正在编写的代码似乎需要它的用例。我想知道编写这样一个宏是否值得付出努力。
有什么建议吗?
答案 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)
只是一些想法。
我有一种感觉,这取决于验证的复杂性和数量,以及功能的性质。
如果您正在进行非常复杂的验证,则应该将验证器从功能中分离出来。原因是您可以使用更简单的方法来构建更复杂的方法。
例如,你写道:
如果您只是进行了大量的简单验证,并且您的问题是冗长的,(例如,您有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)))