clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
donavan 2021-05-18T16:27:01.000300Z

Is there a way to do this:

(s/def ::bar #{:one :two :three})
(s/def ::foo
  (s/with-gen
    (s/keys :req [::bar])
    #(gen/fmap
      (fn [foo]
        (assoc foo ::bar :one))
      (s/gen ::foo))))
without having to create temporary specs like this due to the spec lookup loop
(s/def ::bar #{:one :two :three})
(s/def ::foo*
  (s/keys :req [::bar]))
(s/def ::foo
  (s/with-gen
    ::foo*
    #(gen/fmap
      (fn [foo]
        (assoc foo ::bar :one))
      (s/gen ::foo*))))

alexmiller 2021-05-18T16:33:13.000800Z

You can gen recursive specs

alexmiller 2021-05-18T16:33:57.001500Z

Can you explain why you need with-gen?

donavan 2021-05-18T16:35:21.002200Z

::foo is actually a multi-spec and only the one value of ::bar is valid for this implementation

donavan 2021-05-18T16:35:56.002800Z

So without tweaking the generator it fails to generate valid examples

alexmiller 2021-05-18T16:36:25.003900Z

Have you looked at the generator override map?

donavan 2021-05-18T16:36:30.004100Z

By “You can gen recursive specs” do you mean create the s/def calls dynamically in a macro?

donavan 2021-05-18T16:36:51.004900Z

It’s being fed into another library that does the generating so I can’t supply the override map

alexmiller 2021-05-18T16:37:02.005400Z

No, I mean you can have recursive specs and call gen on them

alexmiller 2021-05-18T16:38:32.006200Z

I think I did not get what you were trying to do

donavan 2021-05-18T16:38:36.006400Z

Haha, so it actually is both ultimately a multi-spec and a recursive one. The recursion ‘within’ the generator works fine if I split the spec into the ::foo* and ::foo defs

donavan 2021-05-18T16:39:17.006600Z

It’s just the call to s/gen within the s/with-gen that loops expectedly

alexmiller 2021-05-18T16:40:33.007300Z

What’s the point of the initial gen if you just override :bar?

1
alexmiller 2021-05-18T16:41:51.008400Z

Aren’t you always generating the same map?

alexmiller 2021-05-18T16:43:01.010300Z

If so, just (s/gen #{ {::bar :one} })

alexmiller 2021-05-18T16:43:30.011500Z

Or gen/return if that is easier to read

donavan 2021-05-18T16:43:31.011600Z

I’m not I follow exactly; the ::bar spec is used in all the multi-specs it’s just that some of the types it’s values are restricted

alexmiller 2021-05-18T16:43:52.012100Z

Is this simplified and there are other keys too?

donavan 2021-05-18T16:44:04.012500Z

Oh, yes sorry this is rather simplified!

alexmiller 2021-05-18T16:44:22.013Z

You left out half the problem and half the constraints

alexmiller 2021-05-18T16:45:20.014400Z

It may not actually be in an inifinite loop - it may just be making very big intermediate colls

donavan 2021-05-18T16:45:35.014800Z

Apologies 🙂 I was just trying to ask the simplest question. I could mull it over some more…

donavan 2021-05-18T16:45:48.015200Z

The simple case above loops forever

alexmiller 2021-05-18T16:46:07.015900Z

If you have any coll-of specs, make sure you set :gen-max 3 on them

alexmiller 2021-05-18T16:46:38.016800Z

There are also some spec dynvars controlling recursion, might want to check those too

alexmiller 2021-05-18T16:47:15.018Z

All that said, there are some outstanding bugs in this area and it’s inherently tricky

donavan 2021-05-18T17:27:16.019800Z

Scratching a little deeper the loop I’m referring to is within the implementation, as spec is currently implemented you cannot define a generator ‘in terms of itself’ like I do above for any of the implementations such as map-spec-impl . It’s deeply baked into the implementation so I suspect it won’t change even though at first glance that seemed like a natural way to scratch my itch 😄 . I’ll re-evaluate what I’m trying to do, thanks @alexmiller

2021-05-18T18:43:29.020600Z

does it make sense to call instrument from a clojure.test fixture? https://clojuredocs.org/clojure.test/use-fixtures ex: if we have tests for various namespaces, each of which should really instrument the specs as they run

alexmiller 2021-05-18T18:43:55.020800Z

yes

alexmiller 2021-05-18T18:44:18.021100Z

and unstrument at the end :)

👍 1
borkdude 2021-05-18T19:55:38.021600Z

@jeffrey.wayne.evans I've also got an fdef-testing library here: https://github.com/borkdude/respeced

2021-05-18T20:41:56.021900Z

nice, thanks. will have a look

2021-05-18T21:19:09.024Z

how about this situation? I want b to be an optional argument to my-fn, but if provided, then it should match some predicate (like map?)? this doesn’t quite work.

(defn my-fn
  [a & [b]]
  ;; do stuff with a and b
)

(s/fdef my-fn
        :args (s/cat :a int? :b map?)
        :ret  map?)

2021-05-18T21:19:25.024300Z

(my-fn 1)
Execution error - invalid arguments to user/my-fn at (form-init6149560983531223969.clj:1).
() - failed: Insufficient input at: [:b]

alexmiller 2021-05-18T21:20:58.024600Z

you spec'ed it as a function taking 2 args (int and map)

alexmiller 2021-05-18T21:21:34.024900Z

you can use s/? to spec an optional part of a regex op

alexmiller 2021-05-18T21:22:34.025900Z

it's a little unclear to me if you meant [b] or b in the my-fn definition

alexmiller 2021-05-18T21:23:20.026600Z

I guess you mean what you said

alexmiller 2021-05-18T21:23:34.027Z

and :args (s/cat :a int? :b (s/? map?)) should work for int and optional map

2021-05-18T21:31:19.027200Z

awesome. thank you, Alex!