(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. 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. Similarily, %
in the :post
condition is always the function return value.
What I would like is to print the value of s1
or %
when either of these respective conditions fail within the AssertionError. 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. I would also like something similar when the return value of the function fails the :post
condition.
This would make it trivial to quickly spot how I misused a function when trying to diagnose from the 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).
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.
You could wrap your predicate with the is
macro from clojure.test
(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. A simpler and less noisy variant of this would be a simple (or (condition-here) (throw-exception-with-custom-message))
syntax, like this:
(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With