【问题标题】:Should I use a function or a macro to validate arguments in Clojure?我应该使用函数还是宏来验证 Clojure 中的参数?
【发布时间】:2010-12-11 01:13:14
【问题描述】:

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

  1. 将验证码写入 函数。
  2. 写一般 目的函数,将其传递给 参数和预期类型。
  3. 编写通用宏, 将参数传递给它 预期类型。
  4. 其他我没想到的。

Larry Hunter 的一些Lisp code 是#3 的一个很好的例子。 (查找 test-variables 宏。)

我的直觉是,宏更合适,因为它可以控制评估,并且有可能进行编译时计算,而不是在运行时全部完成。但是,我还没有遇到我正在编写的似乎需要它的代码的用例。我想知道是否值得努力编写这样的宏。

有什么建议吗?

【问题讨论】:

    标签: validation macros clojure


    【解决方案1】:

    Clojure 已经在fns 上支持(未记录,可能会更改)前置条件和后置条件。

    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 代码看起来很讨厌那个巨大的案例陈述。在我看来,多方法在这里会更好。你自己可以很容易地把这样的东西放在一起。

    (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
    

    【讨论】:

    • 感谢另一个很好的回答。这几乎正​​是我想做的。它还说明了宏是更好的解决方案的原因之一。考虑如果一个函数需要两个或更多具有相同特征的问题参数,比如两个正整数,如何消除问题参数的歧义,宏路由允许您将问题参数的名称放在错误消息中,而不仅仅是值。我认为你不能用函数来做到这一点。
    • 前置条件和后置条件都记录在这里 clojure.org/special_forms 搜索条件映射。
    【解决方案2】:

    只是一些想法。

    我觉得这取决于验证的复杂性和数量,以及函数的性​​质。

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

    例如,你写:

    1. 用于确保列表不为空的验证器,
    2. 用于确保值大于零的验证器,
    3. 使用 1 和 2 确保值是大于零的值的非空列表。

    如果您只是进行大量简单的验证,而您的问题是冗长,(例如,您有 50 个函数都需要非零整数),那么宏可能更有意义。

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

    最后,谈谈“你没想到的其他人”。 Clojure 支持基于调度函数的通用调度 p。该函数可以根据多种因素分派到适当的代码或错误消息。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2015-03-09
        • 1970-01-01
        • 2013-02-17
        • 1970-01-01
        • 1970-01-01
        • 2012-03-06
        • 2017-08-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多