Fork me on GitHub
#clojure-spec
<
2019-06-16
>
hlolli09:06:59

how is it possible to prevent a generator from doing a retry without re-calling a generator function? Here's an ugly phone number generator

(defn phone-number-generator []
  (gen/fmap (fn [[cc phone-num]]
              (str "+" cc " (0) " phone-num))
            (gen/tuple (gen/return (rand-nth ["1" "37" "39" "44" "49"]))
                       (gen/return (-> (* Integer/MAX_VALUE (Math/random))
                                       int
                                       str)))))
printing the validation from the spec
"VALID?" "+1 (0) 265957916" false
it gets print 100 times exactly the same, the generator is only called 1x or 2x that all ends with
Execution error (ExceptionInfo) at clojure.test.check.generators/such-that-helper (generators.cljc:320).
Couldn't satisfy such-that predicate after 100 tries.

gfredericks11:06:27

@hlolli generally you design the generator so that it has no need to retry

gfredericks11:06:50

I can't see the spec so I don't know what that would be

hlolli11:06:28

I see, it's this here

(defn-spec phone-number-valid? boolean?
  [phone-number string?]
  (let [instance (PhoneNumberUtil/getInstance)
        phone-number-parsed (.parse instance phone-number "")]
    (.isValidNumber instance phone-number-parsed)))

(s/def :user/phone-number ;  format
  (s/with-gen phone-number-valid?
    generators/phone-number-generator))

gfredericks11:06:48

okay well there's some stuff buried in the jvm method but for example, is the last portion supposed to be a particular number of digits?

hlolli11:06:04

yes, it depends I think which country code is used, so I just multiply some random number to sizeof(int), should give me a more likely way of number that purely random number generator, I think?

hlolli11:06:32

Or I could choose 1 counry code and substring to a specific length

gfredericks11:06:59

(* Integer/MAX_VALUE (Math/random)) is going to be pretty skewed towards certain lengths

gfredericks11:06:18

if it's easy to get a length associated with each country code, then you could do it much better

hlolli11:06:01

yes, these rules are difficuly, despite all lengths being right, sometimes the first digit must fit some standard

hlolli11:06:07

I can read more about this ofc

hlolli11:06:24

but I thought the generator could try again and again until it's valid

hlolli11:06:31

like with regex specs

gfredericks11:06:42

(gen/for [[country-code length] (gen/elements country-codes-and-lengths)]
          number (apply gen/tuple (repeat length (gen/choose 0 9)))]
  (str "+" country-code " (0) " number))
is the sort of thing I would aim for if I Had that list

hlolli11:06:07

cool, instersting 🙂

gfredericks11:06:12

relying on the generator trying again is not a good plan unless you're sure it's highly likely to succeed

gfredericks11:06:27

because you can easily be in a situation where it only succeeds e.g. 0.001% of the time

gfredericks11:06:36

and then you'll be burning a lot of CPU

hlolli11:06:22

yes true, I thought the idea would be to give the generator a bit of help. The odds of alpha-numberic random gen hitting a valid phone number are probably astronomical, I'd assume

gfredericks11:06:46

yeah, you can definitely do worse than what you have 🙂

gfredericks12:06:59

@hlolli incidentally, here's a generator that's evenly distributed between digit lengths

(gen/for [digit-count (gen/choose 5/10)
          digits (apply gen/tuple (repeat digit-count (gen/choose 0 9)))]
  digits)

gfredericks12:06:08

that might work better for you

hlolli12:06:33

ok, but I didn't get gen/for, in which namespace is it?

hlolli12:06:26

I'll try soon and let you know, I'm generating fake firebase users for testing

gfredericks12:06:15

clojure.test.check.generators

gfredericks12:06:47

it's just syntax sugar for gen/fmap and gen/bind mostly

gfredericks12:06:59

you can do the above just using gen/bind

hlolli12:06:57

ah I see, I was searching for it in clojure.spec.gen.alpha

hlolli12:06:50

thanks 🙂