clojure-spec

onetom 2022-04-20T03:20:02.839689Z

i wanted to generate some datomic lookup-refs with spec, but s/cat always returns a seq, not a vector. is there some built-in / idiomatic / recommended way to spec vectors? i saw there are :kind and :into options for s/coll-of, but i couldn't combine it with s/cat; i always got Couldn't satisfy such-that predicate after 100 tries. error. i found a recommendation here: https://gist.github.com/mhuebert/8fdeedae57bf797778054dcf8f33ab8b but my gut feeling tells me, that there might be a simpler way.

Alex Miller (Clojure team) 2022-04-20T03:26:52.914829Z

unfortunately, there is not a simpler way - it is very challenging (in spec 1) to spec vectors and get all of conform, unform, gen etc to work. we've fixed this in spec 2 but I don't have a good answer in spec 1.

🙏 1
Kelvin 2022-04-20T13:27:55.920649Z

Would s/tuple work?

Alex Miller (Clojure team) 2022-04-20T13:46:11.624129Z

for some limited cases, yes (it's fixed size and can only be positionally described, not with the regex ops)

onetom 2022-04-20T20:50:31.665889Z

I just started looking into spec2. Found this page, which specifically mentions how to solve my issue with non-flowing s/and-: https://github.com/clojure/spec-alpha2/wiki/Differences-from-spec.alpha (s/def ::vcat (s/and- (s/cat :i int?) vector?)) Thanks @alexmiller for the great docs!

Alex Miller (Clojure team) 2022-04-20T21:03:57.748829Z

not mentioned there, but that's actually reified for you in s/catv (vector-only) and s/cats (sequence-only) in spec 2

Alex Miller (Clojure team) 2022-04-20T21:04:46.328899Z

I don't think that ever made it back to those docs, but they're there

Alex Miller (Clojure team) 2022-04-20T21:05:05.573149Z

and before you ask, no I would not recommend using spec 2 at this time, it has a number of bugs :)

👍 1
🙂 1
Cam Saul 2022-04-20T22:51:22.428679Z

Is there a way to pass along/access/use the overrides from a top-level call to s/gen inside a custom generator defined with s/with-gen or (s/spec ... :gen ...)? Example. Supposed I want to generate some sort of query with the keys :database and :table. I'd like to have a custom generator for :table so its values are based on the value of :database... however if I use an overriden generator for :database That generator is invisible from inside my custom generator.

(s/def ::database string?)

(s/def ::table
  (s/with-gen
    string?
    (fn []
      (gen/bind
       (s/gen ::database)
       (fn [db]
         (gen/fmap (fn [table]
                     (str db \. table))
                   gen/string))))))

(s/def ::query
  (s/keys :req-un [::database ::table]))

(gen/sample (s/gen ::query {::database (fn [] (gen/return "my_database"))}))
;; =>
[{:database "my_database", :table "."}
 {:database "my_database", :table "7.$"}
 {:database "my_database", :table "T2."}
 {:database "my_database", :table "xv.H¨"}
 {:database "my_database", :table "aB.Ín"}
 {:database "my_database", :table "Q3MCh.Çá"}
 {:database "my_database", :table "F68.·%:"}
 {:database "my_database", :table ".>ì"}
 {:database "my_database", :table "jBk.ÇP µrfL"}
 {:database "my_database", :table "d.¥¶«â\b"}]
Is there some way to do this? I know I could do the whole thing with test.check.generators/let or bind but I'd be missing out on all the niceness from spec maps since AFAIK there's nothing like s/keys in test.check

2022-04-21T15:03:10.671629Z

I don't think there's any way provided in spec to make your custom generator see the override (you could get creative and bind a dynamic var, but that's outside of specs functionality and probably a bit unexpected). test.check does have hash-map, which will generate a map of keys to values like (gen/hash-map :database db-gen :table table-gen). I would probably use fmap though here and do something like:

(gen/fmap (fn [[db-str table-str]] {:table (str db-str \. table-str) : database db-str})
           (gen/tuple (gen/return "my_database") gen/string))
Most things can be solved using fmap and then reconciling the generated parts in the fmap fn somehow. This gives better shrinking as well than using bind/let.

Cam Saul 2022-04-21T21:20:35.847999Z

I tried binding dynamic vars at every possible place but couldn't figure out how to get it to work consistently. Thanks, I think I'll just stick to using test.check stuff without spec for now. I managed to put together a generator that works sort of like s/keys with :opt or :opt-un , not sure how well it's going to handle the shrinking. I'll have to wrap my head around that a bit more

Cam Saul 2022-04-20T23:20:36.651949Z

I guess I could get it working by redefining the entire spec with s/def instead of trying to pass an override for the generator, or maybe just with-redefs -ing clojure.spec.alpha/registry-ref itself... both feel icky but since this just for generative testing it's probably not the end of the world.

Cam Saul 2022-04-20T23:22:38.687299Z

Another option might be to replace all my specs with functions that return specs based on previous state, but for map specs I guess I'd have to use Spec Alpha 2 so I could programmatically create them with schema or the like. Maybe that's a bit better. But creating a bunch of ad-hoc specs programatically actually seems like more work than if I forgot about spec altogether and just used test.check.generators directly