Fork me on GitHub
#clojure-spec
<
2021-01-30
>
hadils17:01:15

Is there any documentation on clojure.spec.alpha.gen (spec2)?

seancorfield17:01:58

@hadilsabbagh18 Spec 2 is not ready for use yet. It's still evolving and it has a number of bugs right now.

seancorfield17:01:15

(and the only documentation for it is what you'll find in the repo and the auto-generated API docs: https://clojure.github.io/spec-alpha2/ )

hadils18:01:13

Thanks @alexmiller -- I have already read those websites. I was asking about gen specifically.

hadils18:01:19

@alexmiller -- my IDE indicates differently. I am requiring clojure.alpha.spec.gen -- e.g. it does not have fmap

Alex Miller (Clojure team)18:01:38

That’s doesn’t seem right

Alex Miller (Clojure team)19:01:45

I think your ide is wrong :)

Alex Miller (Clojure team)19:01:05

No changes here from spec 1

Alex Miller (Clojure team)19:01:32

But it is generating those defs as wrappers so a static analyzer wouldn’t see them

Alex Miller (Clojure team)19:01:55

If you load the ns you should see those vars though

hadils19:01:06

I have a different sha. Let me try yours and see what happens...

hadils19:01:07

I tried it and it works now. Thanks for all your help.

Greg Rynkowski21:01:14

Hi, I’m trying to create a time generator based on given time range. I thought about using s/int-in but distribution for default generator for this spec is not very useful. E.g.

(gen/sample (s/gen (s/int-in 1 1000000000)))
=> (2 1 2 5 5 6 15 2 3 4)
(the range is from 1 to bilion while all sample results are leaning to beginning of the range) Are there any built-in generators I could use that can give me better distributed results?

Greg Rynkowski21:01:07

Oh, ok, I just realised that I get better results when I’m calling gen/generate. Weird. 🤷

andy.fingerhut22:01:39

I am not sure if what I am about to describe is what is happening in your case, but note that many spec generators try to generate "small test cases" first, then "larger/more complex test cases" only after those pass. For numbers, that sometimes means generating smaller values first, and only when those test cases pass, larger values. Default generators can be overridden if you don't like how they work.

andy.fingerhut22:01:28

The idea being if that "small" test cases fail, those are usually easier to debug the root cause and fix it, versus seeing large complex failing test cases.

Greg Rynkowski22:01:49

Could be, I don’t know. I end up creating custom integers generator using MersenneTwister PRNG.

Greg Rynkowski22:01:11

(defn gen-int-in
  [{:keys [min max]}]
  (gen/fmap
    (fn [_] (-> (twist/next-int) (mod (- max min)) (+ min)))
    (gen/return :something)))

(defn millis->date-time
  [millis]
  (-> millis
      (Instant/ofEpochMilli)
      (.atZone (ZoneId/systemDefault))
      .toLocalDateTime))

(defn gen-local-date-time
  [[from to]]
  (gen/fmap
    millis->date-time
    (gen-int-in {:min from :max to})))
sample execution:
(gen/sample (gen-int-in {:min 1 :max 1000000000}) 15)
=>
(242825682
 150977944
 334804692
 989355517
 482024216
 627890233
 163007490
 227494133
 464164671
 747363986
 762489210
 261499033
 582274967
 50926907
 472313391)

(gen/sample (gen-local-date-time (create-future)))
=>
(#object[java.time.LocalDateTime 0x4139290 "2021-02-22T13:25:24.531"]
 #object[java.time.LocalDateTime 0x53185984 "2021-01-31T00:17:03.320"]
 #object[java.time.LocalDateTime 0xcb5632b "2021-02-23T02:22:41.585"]
 #object[java.time.LocalDateTime 0x773ccacd "2031-01-23T16:49:36.048"]
 #object[java.time.LocalDateTime 0x6fa351a8 "2021-02-09T10:45:48.003"]
 #object[java.time.LocalDateTime 0x7ee6f1a1 "2031-01-29T13:07:57.477"]
 #object[java.time.LocalDateTime 0x596d796d "2021-02-20T11:15:51.061"]
 #object[java.time.LocalDateTime 0x4896aa2f "2031-01-18T13:37:21.066"]
 #object[java.time.LocalDateTime 0x3dcd9c84 "2021-02-23T23:50:04.910"]
 #object[java.time.LocalDateTime 0x746f0965 "2021-02-21T22:55:50.011"])
(`create-future` returns pair of timestamps [now, 10-years-from-now])

Greg Rynkowski15:02:20

@U0CMVHBL2 re your answer https://clojurians.slack.com/archives/C1B1BB2Q3/p1612044579020800?thread_ts=1612040474.020100&amp;cid=C1B1BB2Q3: This is exactly the case I was experiencing two days ago. I didn’t understand how generators work. This talk was eye opening: https://www.youtube.com/watch?v=F4VZPxLZUdA. Creating a custom generator was an overkill, simple gen/scale was enough for what I needed.

andy.fingerhut15:02:33

Cool. Glad you are finding ways to get the behavior you are hoping for.

andy.fingerhut15:02:30

The "small is simpler and should be generated earlier" makes sense for some numeric inputs, e.g. if your Fibonacci function has bugs for small input values, you probably want to debug those first before figuring out why it fails when given 10,325 as input. But for your use case of date/time ranges, in most applications it seems like the values early in the range are no simpler than any others (I imagine there could be date/time use cases where just-inside and just-outside of acceptable ranges are important corner cases to ensure that you test).