How do you use an existing vector of predicates with :post conditions in Clojure?

Given that :post takes a form that gets evaluated later (e.g. {:post [(= 10 %)]}). How could one dynamically pass a 'pre-made' vector of functions to :post?

For example:

(def my-post-validator
  [prediate1 predicate2 predicate3])
   

(defn foo [x] 
  {:post my-post-validator}
  x)

this throws a syntax error

Don't know how to create ISeq from: clojure.lang.Symbol

With my fuzzy understanding, it's because defn is a macro, and the thing that allows the % syntax in :post is that it's quoted internally..?

I thought maybe I then use a macro to pass a 'literal' of what I wanted evaluated

(defmacro my-post-cond [spec]
  '[(assert spec %) (predicate2 %) (predicate n)])

example:

(defn foo [x]
  {:post (my-post-cond :what/ever)}
  x)

However, this attempt gives the error:

Can't take value of a macro

Is there a way to pass a vector of things to :post rather than having to define it inline?

Upvotes: 0

Views: 66

Answers (2)

Alan Thompson
Alan Thompson

Reputation: 29976

I started off as a fan of pre- and post-conditions, but I've changed over the years.

For simple things, I prefer to use Plumatic Schema to not only test inputs & outputs, but to document them as well.

For more complicated tests & verifications, I just put in an explicit assert or similar. I also wrote a helper function in the Tupelo library to reduce repetition, etc when debugging or verifying return values:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn oddly
  "Transforms its input. Throws if result is not odd"
  [x]
  (let [answer (-> x (* 3) (+ 2))]
    (with-result answer
      (newline)
      (println :given x)
      (assert (odd? answer))
      (println :returning answer))))

(dotest
  (is= 5 (oddly 1))
  (throws? (oddly 2)))

with result

------------------------------------
   Clojure 1.10.3    Java 11.0.11
------------------------------------

Testing tst.demo.core

:given 1
:returning 5

:given 2

Ran 2 tests containing 2 assertions.
0 failures, 0 errors.

Passed all tests

So with either the println or assert, the returned value is easy to see. If it fails the assert, an Exception is thrown as normal.

Upvotes: 0

Eugene Pakhomov
Eugene Pakhomov

Reputation: 10727

You can't pass a vector of predefined predicates, but you can combine multiple predicates under a single name and use that name in :post:

(defn my-post-cond [spec val]
  (and
    ;; Not sure if this is exactly what you want,
    ;; given that `val` becomes an assert message.
    (assert spec val)
    (predicate2 val)
    ;; You used `n` - I assume it was supposed to be `%`.
    (predicate val)))

(defn foo [x]
  {:post [(my-post-cond :what/ever %)]}
  x)

Upvotes: 1

Related Questions