Clojure.spec for functions by example: their inputs, outputs, and the relationships between them. Updated 9/6/2018.

Simple Input & Output

We have a simple function that takes a single argument and returns a set:

(defn digits
  "Takes just an int and returns the set of its digit characters."
  [just-an-int]
  (into #{} (str just-an-int)))

Function specs are typically defined with s/fdef. Let’s provide a spec for the arguments:

(s/fdef digits :args (s/cat :just-an-int int?))

Note: with s/cat the function’s argument names and the keywords used to describe them in the spec don’t have to match — it’s the position that’s important.

Function argument lists can be spec’d as a sequence, which makes perfect sense as they are a sequence of arguments. I often prefer s/cat, treating the arguments as a tagged concatenation, although :args specs can also be described with s/tuple, s/alt, s/+, s/*, s/?, etc.

We can now instrument the function to assert calls against its :args spec:

(stest/instrument `digits)

This only covers the function’s inputs. We can also spec the return value:

(s/fdef digits
        :args (s/cat :just-an-int int?)
        :ret (s/coll-of char? :kind set? :min-count 1))

With a :ret spec, we can now check the function against our spec with generative tests:

(stest/check `digits)

Keyword Arguments

There’s a s/keys* macro that works for kwargs, similarly to how s/keys works for maps.

(defn big-fun
  [why when & {:keys [who which what]}]
  (format "%s %s %s %s %s" why when who which what))

(s/fdef big-fun
        :args (s/cat :why string?
                     :when #(instance? LocalDateTime %)
                     :kwargs (s/keys* :req-un [(or ::who
                                                   (and ::which ::what))])))

(stest/instrument `big-fun)
(big-fun "because" (LocalDateTime/now) :which 1 :what 4) ;; valid
(big-fun "said so" (LocalDateTime/now) :who "I")         ;; also valid
(big-fun "because" (LocalDateTime/now) :which 1)         ;; invalid

Here we defined a kwargs spec that requires either a :who key or a :which key and :what key, just like we could’ve done for a map.

Multi-arity & Variadic Functions

We can spec multi-arity functions using s/alt to define the alternate cases, and variadic functions using s/*. Here’s a function with two fixed arities and one variadic arity:

(defn such-arity
  ([] "nullary")
  ([one] "unary")
  ([one two & many] "one two many"))

(s/fdef such-arity
        :args (s/alt :nullary (s/cat)
                     :unary (s/cat :one any?)
                     :variadic (s/cat :one any?
                                      :two any?
                                      :many (s/* any?))))

We can spec the zero-arity with (s/cat); no arguments. We specify the variadic arguments with s/*. The “regex” specs compose to match a single sequence. This composition of s/cat and s/* produces a spec that wants a sequence of two arguments and then any number of additional arguments.

And here’s a variadic function with positional destructuring:

(defn slarp [path & [blurp? glurf?]]
  (str path blurp? glurf?))
(s/fdef slarp
  :args (s/cat :path string?
               :rest (s/? (s/cat :blurp? int?
                                 :glurf? boolean?))))
(s/exercise-fn `slarp)
=>
([("") ""]
 [("" -1 false) "-1false"]
 [("e7" 0 true) "e70true"]
 [("du9") "du9"]
 [("K1" -6 false) "K1-6false"]
 [("op0" -3 false) "op0-3false"]
 [("2y" 2 false) "2y2false"]
 [("qaGWVK") "qaGWVK"]
 [("9h1Rh") "9h1Rh"]
 [("") ""])

Higher-Order Functions

This is an only slightly different, more involved example than the one in the clojure.spec guide.

Here’s a spec for a function that takes any two arguments and returns one of them:

(s/def ::pick-fn
  (s/with-gen
    (s/fspec :args (s/cat :a any? :b any?)
             :ret any?
             :fn #(or (= (:ret %) (:a (:args %)))
                      (= (:ret %) (:b (:args %)))))
    #(gen/return (fn [a b] (rand-nth [a b])))))

This function spec is registered to a qualified keyword rather than a function symbol via s/fdef. This is defined with s/def and s/fspec because I want to use this function spec in more than one place, and specify a custom generator. Notice the additional :fn spec that asserts the returned value is one of the input values. This spec will be checked when we check the function with generative tests.

I use s/with-gen to specify a custom generator that returns a function that returns one of its arguments randomly. I could however just allow spec to create a function generator from my function spec; the generated functions would return random samples of its :ret spec, and any calls would have their arguments asserted against its :args spec. A spec-generated function would not be desirable in this contrived example because we want to ensure the function always returns something it was given.

How about this for some implementation of ::pick-fn:

(defn battle
  "Returns the victor."
  [contestant-1 contestant-2]
  (if (even? (System/nanoTime)) contestant-1 contestant-2))

Now I want to associate the ::pick-fn spec with battle function. We can achieve the same effect as (s/fdef battle ...) with s/def:

(s/def battle ::pick-fn)
(stest/instrument `battle)
(stest/check `battle)

Now let’s write a higher-order function to take a ::pick-fn-conformant function and a collection:

(defn winners
  "Uses f to pick a successor between each element of coll, returning sequence of successors."
  [f coll]
  (rest (reductions f coll)))
(winners battle (range 20))
;;=> (0 0 0 0 4 4 6 6 6 6 10 10 10 10 14 14 14 14 14 19)

And a spec for the function:

(s/fdef winners
        :args (s/cat :f ::pick-fn
                     :coll (s/coll-of any? :min-count 2))
        :ret coll?
        :fn (fn [{:keys [args ret]}]
              (and (= (count ret) (dec (count (:coll args))))
                   (set/subset? (set ret) (set (:coll args))))))
(stest/instrument `winners)
(stest/check `winners)

The winners function has some odd invariants. The f arg must conform to ::pick-fn and coll must have at least two elements. Additionally, the relationship between inputs and outputs for winners is expressed in the :fn spec:

  1. It should always return a collection that has one less element than the input collection. This is because the first “pick” is for the first two input elements.
  2. The returned elements should be a subset of the input elements.

Warning

Any function g you pass into a higher-order, instrumented function f may be invoked multiple times as spec tests its conformance! This might be a problem if g is expensive or has side effects.

(defn f [g] (g 1))
(s/fdef f :args (s/cat :g (s/fspec :args (s/tuple int?))))
(st/instrument `f)

Now if we call f with a side-effecting function (printing text) we can see the given function invoked many times with random inputs — a sort of miniature quick-check.

(f #(doto % prn inc))
-1
0
-2
...
59
-69770
-1165
1
=> 1

Possible Workarounds

Simplify the higher-order function’s spec:

(s/fdef f :args (s/cat :g ifn?))

Or disable the behavior using a dynamic binding in spec:

(binding [clojure.spec.alpha/*fspec-iterations* 0]
  (f #(doto % prn inc)))
1
=> 1

Run-time Checking

Instrumentation

You can call instrument with no arguments to instrument all loaded/instrumentable vars, and you may want to do this at dev/test time but not in production to avoid performance penalties. One convenient solution is to have a separate dev/test entrypoint in your program that calls instrument after the relevant namespaces are loaded. Another option with Leinengen is to use :injections in your project.clj under specific profile(s):

:injections [(require 'lib.core) ;; all instrumented fns should be loaded here
             (require 'clojure.spec.test.alpha)
             (clojure.spec.test.alpha/instrument)]

Spec’s instrument only checks function inputs (:args specs). Alternatively, the Orchestra library has a drop-in replacement that also checks :ret and :fn specs.

:pre and :post checks

This will throw an AssertionError if called with any non-nil, non-string value:

(defn stringer-bell
  "Prints a string and rings bell."
  [s]
  {:pre [(s/valid? (s/nilable string?) s)]}
  (println s "\007"))

Asserting

There’s also s/assert which can be used in function bodies:

(defn stringer-bell [s]
  (s/assert (s/nilable string?) s)
  (println s "\007"))

You need to enable spec’s off-by-default assertion checking for this to have an effect:

(s/check-asserts true)

s/assert will throw a more detailed exception than a :pre/:post condition, although be careful not to use s/assert there: it returns valid values and if the spec allows nil, the condition will fail when a valid nil is passed.