I'm trying to use spec to build a generator for valid destructure bindings. I'm starting with the specs in clojure.core.specs.alpha.
(s/def ::seq-binding-form
(s/and vector?
(s/cat :forms (s/* ::binding-form)
:rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form))
:as-form (s/? (s/cat :as #{:as} :as-sym ::local-name)))))
Generating ::seq-binding-form values struggles because it's just generating random vectors that hopefully satisfy the s/cat. This seems to get fixed with (gen/fmap vec ...):
(s/def ::seq-binding-form
(s/with-gen
(s/and vector?
(s/cat :forms (s/* ::binding-form)
:rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form))
:as-form (s/? (s/cat :as #{:as} :as-sym ::local-name))))
#(gen/fmap
vec
(s/gen
(s/cat :forms (s/* ::binding-form)
:rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form))
:as-form (s/? (s/cat :as #{:as} :as-sym ::local-name)))))))
Unfortunately, the version using gen/fmap now usually throws a stackoverflow exception. However, I noticed that if I ignore the vector requirement for ::seq-binding-form, then the stack overflow exception goes away.
(s/def ::seq-binding-form
(s/cat :forms (s/* ::binding-form)
:rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form))
:as-form (s/? (s/cat :as #{:as} :as-sym ::local-name))))
Is there a way to prevent and stackoverflow exceptions and have it generate a vector?It looks like spec2 might have better support for this. I don't have any real reason to stick with spec1, so I'll see if I can get this working with spec2.
What if instead of fmap you use and conforming?
I don't know about conforming, but I'll take a look
Maybe it's called conform, lemme check
I was checking out spec2 based off this previous thread, https://clojurians.slack.com/archives/C1B1BB2Q3/p1650425212914829?thread_ts=1650424802.839689&cid=C1B1BB2Q3
giving it a try!
I don't think that solves this use case. It still doesn't generate ::seq-binding-form as a vector. It does convert it if I conform the value, but I think it's worth trying spec2
Gen doesn't emit conformed values?
It's totally possible I'm doing it wrong. My attempt was:
(s/def ::seq-binding-form
(s/and
(s/cat :forms (s/* ::binding-form)
:rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form))
:as-form (s/? (s/cat :as #{:as} :as-sym ::local-name)))
(s/conformer vec)))the specs need to be updated to have this work with spec 2
to use catv instead of cat etc
It works!
Just to follow up, test.check is magic. For the right problems, it finds tricky use cases that I would never think of.
> (let [[ & [ & [& [& [& [& hi]]]]]] [42]]
hi)
(42)
> (let [[& {:as m}] [:a 42]]
m)
{:a 42}generation isn't magic, but I'm pretty sure shrinking is