Welcome! Please see the About page for a little more info on how this works.

0 votes
in Spec by

I was experimenting with custom function generators in order to support the scenario describe in spec guide https://clojure.org/guides/spec#_combining_check_and_instrument but with the goal of making the generated values depend on the concrete arguments and not only on the :ret spec.

While working on that, I noticed a weird behavior that I fail to understand where a generated function is being called 21 times before actually being returned.

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

(s/fdef foo
  :args (s/cat :x int?)
  :gen #(gen/return
         (fn [& argv]
           (prn argv)
           (gen/generate (s/gen string?)))))

(gen/generate (s/gen `foo))
;; Prints:
;; (-1)
;; (-1)
;; (-2)
;; (-1)
;; (3)
;; (-2)
;; (-2)
;; (-1)
;; (-35)
;; (14)
;; (-16)
;; (26)
;; (-17)
;; (150)
;; (0)
;; (-1)
;; (25)
;; (5638)
;; (543)
;; (57)
;; (257)
;; Returns: #function[fspec-gen-bug.core/fn--6577/fn--6578]

This behavior seems specific to function generation for example

(s/def ::bar
  (s/spec (s/coll-of int?)
          :gen #(gen/return
                 (doto [(gen/generate (s/gen int?))]

(gen/generate (s/gen ::bar))
;; Prints:
;; [438803]
;; Returns: [438803]

Is there a good reason why generated functions are invoked eagerly ? or is it a bug ?

1 Answer

0 votes

After some extra investigation, I have understood that s/conform* is invoked when generating the function via s/valid? and that 21 is the default*fspec-iterations* value.

I was a bit surprised by the design choice of checking with full-instrumentation meaning with :args, :ret and :fn at function generation time, and only :args at function execution time.

but after a second thought since every function is associated with a generator automatically, it is important to not silently generate a function that do not satisfy the :fn property of the spec. Moreover since generating function purpose is to serve as stub checking only :args at execution time for correct error boundary checking is a good thing.

We have a ticket and some ideas about alternative impls for this in spec 2.