简体   繁体   English

我怎样才能让 Clojure :pre & :post 报告它们的失败值?

[英]How can I get Clojure :pre & :post to report their failing value?

(defn string-to-string [s1] 
  {:pre  [(string? s1)]
   :post [(string? %)]}
  s1)

I like :pre and :post conditions, they allow me to figure out when I have put "square pegs in round holes" more quickly.我喜欢 :pre 和 :post 条件,它们使我能够更快地弄清楚何时将“方钉放入圆孔中”。 Perhaps it is wrong, but I like using them as a sort of poor mans type checker.也许这是错误的,但我喜欢将它们用作一种穷人类型检查器。 This isn't philosophy though, this is a simple question.但这不是哲学,这是一个简单的问题。

It seems in the above code that I should easily be able to determine that s1 is a function argument in the :pre condition.在上面的代码中,我应该很容易确定s1:pre条件中的函数参数。 Similarily, % in the :post condition is always the function return value.类似地, :post条件中的%始终是函数返回值。

What I would like is to print the value of s1 or % when either of these respective conditions fail within the AssertionError.我想要的是当这些条件中的任何一个在 AssertionError 中失败时打印s1%的值。 So I get something like所以我得到类似的东西

(string-to-string 23)

AssertionError Assert failed: (string? s1) 
(pr-str s1) => 23 

With the AssertionError containing a single line for every variable that was identified as being from the function argument list and that was referenced in the failing test. AssertionError 包含一行,用于标识为来自函数参数列表并在失败测试中引用的每个变量。 I would also like something similar when the return value of the function fails the :post condition.当函数的返回值失败:post条件时,我也想要类似的东西。

This would make it trivial to quickly spot how I misused a function when trying to diagnose from the AssertionError.这将使快速发现我在尝试从 AssertionError 进行诊断时如何滥用函数变得微不足道。 It would at least let me know if the value is nil or an actual value (which is the most common error I make).它至少会让我知道该值是nil还是实际值(这是我犯的最常见的错误)。

I have some ideas that this could be done with a macro, but I was wondering if there was any safe and global way to basically just redefine what (defn and (fn and friends do so that :pre and :post would also print the value(s) that lead to the test failing.我有一些想法,这可以用宏来完成,但我想知道是否有任何安全和全局的方法来基本上重新定义什么(defn(fn和朋友这样做:pre:post也会打印值(s) 导致测试失败。

You could wrap your predicate with the is macro from clojure.test你可以用clojure.testis宏来包装你的谓词

(defn string-to-string [s1] 
  {:pre  [(is (string? s1))]
   :post [(is (string? %))]}
 s1)

Then you get:然后你得到:

(string-to-string 10)
;FAIL in clojure.lang.PersistentList$EmptyList@1 (scratch.clj:5)
;expected: (string? s1)
;actual: (not (string? 10))

@octopusgrabbus kind of hinted at this by proposing (try ... (catch ...)) , and you mentioned that that might be too noisy, and is still wrapped in an assert. @octopusgrabbus 通过提议(try ... (catch ...))暗示了这一点,并且您提到这可能太吵了,并且仍然包含在断言中。 A simpler and less noisy variant of this would be a simple (or (condition-here) (throw-exception-with-custom-message)) syntax, like this:一个更简单且噪音更小的变体是一个简单的(or (condition-here) (throw-exception-with-custom-message))语法,如下所示:

(defn string-to-string [s1] 
  {:pre  [(or (string? s1)
              (throw (Exception. (format "Pre-condition failed; %s is not a string." s1))))]
   :post [(or (string? %)
              (throw (Exception. (format "Post-condition failed; %s is not a string." %))))]}
  s1)

This essentially lets you use pre- and post-conditions with custom error messages -- the pre- and post-conditions are still checked like they normally would be, but your custom exception is evaluated (and thus thrown) before the AssertionError can happen.这基本上允许您使用带有自定义错误消息的前置和后置条件——前置和后置条件仍然像往常一样检查,但在 AssertionError 发生之前评估(并因此抛出)你的自定义异常。

Something like below where clojure spec is explaining the problem?类似于下面的 clojure 规范在解释问题的地方? This will throw an assertion error which you can catch.这将抛出一个您可以捕获的断言错误。

 (defn string-to-string [s1] 
  {:pre [ (or (s/valid?  ::ur-spec-or-predicate s1) 
              (s/explain ::ur-spec-or-predicate s1)]}
  s1)

Clojure spec can be used to assert on arguments, yielding an exception on invalid input with data explaining why the failure occurred (assertion checking has to be turned on): Clojure 规范可用于对参数进行断言,在无效输入上产生异常,其中包含解释失败原因的数据(必须打开断言检查):

(require '[clojure.spec.alpha :as s])

;; "By default assertion checking is off - this can be changed at the REPL
;;  with s/check-asserts or on startup by setting the system property
;;  clojure.spec.check-asserts=true"
;;
;; quoted from https://clojure.org/guides/spec#_using_spec_for_validation
(s/check-asserts true)

(defn string-to-string [s1] 
  {:pre  [(s/assert string? s1)]
   :post [(s/assert string? %)]}
  s1)

(string-to-string nil) => #error{:cause "Spec assertion failed\nnil - failed: string?\n",
                                 :data #:clojure.spec.alpha{:problems [{:path [], :pred clojure.core/string?, :val nil, :via [], :in []}],
                                                            :spec #object[clojure.core$string_QMARK___5395 0x677b8e13 "clojure.core$string_QMARK___5395@677b8e13"],
                                                            :value nil,
                                                            :failure :assertion-failed}}

The [:data :value] key in the exception shows you the failing value.异常中的[:data :value]键显示失败的值。 The [:data :problems] key shows you why spec thinks the value is invalid. [:data :problems]键显示了为什么规范认为该值无效。 (In this example the problem is straightfoward, but this explanation gets very useful when you have nested maps and multiple specs composed together.) (在这个例子中,问题很简单,但是当您将嵌套地图和多个规范组合在一起时,这个解释会非常有用。)

One important caveat is that s/assert when given valid input returns that input, yet the :pre and :post conditions check for truthiness.一个重要的警告是s/assert在给定有效输入时返回该输入,但:pre:post条件检查真实性。 If the validation conditions you need consider falsy values to be valid, then you need to adjust your validation expression, otherwise s/assert will succeed, but the truthiness check in :pre or :post will fail.如果您需要的验证条件认为假值有效,那么您需要调整您的验证表达式,否则s/assert将成功,但:pre:post的真实性检查将失败。

(defn string-or-nil-to-string [s1]
  {:pre [(s/assert (s/or :string string? :nil nil?) s1)]
   :post [(s/assert string? %)]}
  (str s1))

(string-or-nil-to-string nil) => AssertionError

Here's what I use to avoid that problem:这是我用来避免该问题的方法:

(defn string-or-nil-to-string [s1]
  {:pre [(do (s/assert (s/or :string string? :nil nil?) s1) true)]
   :post [(s/assert string? %)]}
  (str s1))

(string-or-nil-to-string nil) => ""

Edit: enable assertion checking.编辑:启用断言检查。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM