Fork me on GitHub
#clojure-spec
<
2018-01-29
>
Roman Liutikov12:01:15

👋 Given a naive example of a function spec, how to specify a custom generator such that it satisfies :fn spec?

(s/fdef sbtrct
        :args (s/cat :a int? :b int?)
        :ret int?
        :fn #(= (:ret %) (- (-> % :args :a) (-> % :args :b))))

conan17:01:56

what is it you want to generate exactly?

conan17:01:21

are you trying to generate a 2-value vector containing ints that can be used with apply as the args for sbtrct?

conan17:01:16

generators generate values, so by providing a spec for each of your input args and for your return value, you've already provided generators (because spec provides a built-in generator for the int? predicate)

Roman Liutikov17:01:31

@U053032QC I want to generate a sequence of args/ret values such that they satisfy relation defined in :fn spec

conan17:01:16

ah, ok:

(s/exercise-fn `sbtrct)

conan17:01:12

that'll generate a bunch of input values for a and b (using the generators provided by their specs, i.e. int?), put them into your function, and return you a sequence of those input pairs with the calculated output

conan17:01:32

if your function doesn't match your :fn spec, it'll blow up

conan17:01:36

if you want to do a larger number of tests of your function, you can do generative testing using clojure.spec.test.alpha/check

conan17:01:51

so

(stest/check `sbtrct`)

conan17:01:22

that'll generate 1000 different inputs and check all the outputs for conformance with your :ret spec and :fn predicate

conan17:01:24

neat, huh?

Roman Liutikov17:01:56

> if you want to do a larger number of tests of your function.. that’s exactly what I need, exercise-fn doesn’t seem to be able to generate huge sequences 🙂

conan17:01:27

you can do

(s/exercise-fn `sbtrct 100000)

conan17:01:41

oh no, looks like that blows up

Roman Liutikov17:01:57

yeah I’m getting integer overflow

conan17:01:15

doesn't blow up so long as you don't print it

conan17:01:05

might just be lazy though

conan17:01:17

yeah it's lazy

conan17:01:26

so there are numbers in there that blow up

Roman Liutikov17:01:06

hm, what can be done about this?

conan17:01:34

do you need the ints to be very large?

Roman Liutikov17:01:54

no, just a lot of them

conan17:01:59

if not, you could try

(s/def ::small-int (s/int-in 0 1000000))
(s/fdef sb
  :args (s/cat :a ::small-int :b ::small-int)
  :ret int?
  :fn #(= (:ret %) (- (-> % :args :a) (-> % :args :b))))

conan17:01:12

that's specifying a range that the ints can be in, so you won't get huge numbers generated

Roman Liutikov17:01:39

Should I use with-gen if I don’t want to modify the original spec?

conan17:01:40

with-gen is for when you need to create your own generator because the existing ones can't capture your set of values

conan17:01:04

i guess you'll have to look into what's actually causing the error

conan17:01:28

that approach above works for me though, with s/int-in as the spec for the inputs to your function

conan17:01:39

i used a different name for my function though so don't just copy/paste

Roman Liutikov17:01:04

works for me as well, thanks!

conan17:01:54

no worries, docs are often a bit sparse around spec, but it is alpha, so /shrug

Roman Liutikov18:01:04

I ended up with (s/int-in Integer/MIN_VALUE Integer/MAX_VALUE)

john18:01:59

Does spec.test in any way supersede test.check? I couldn't find docs explaining the difference. There seems to be some overlap between the two.

arrdem18:01:23

Not that I know of. spec.test leverages test.check extensively and seems designed to complement test.check by enabling you to derive many generators automatically rather than specify them by hand.

Alex Miller (Clojure team)18:01:34

correct. test.check is useful for writing arbitrary property-based tests and creating different kinds of custom generators

bronsa18:01:40

and spec.test uses test.check internally

john19:01:03

Understood. Thanks