Fork me on GitHub
#test-check
<
2019-02-18
>
borkdude09:02:34

I wanted to generate arguments for subs:

(def subs-args-gen
  (gen/fmap
   (fn [[str [start end]]]
     (if end
       [str start end]
       [str start]))
   (gen/bind
    (gen/string)
    (fn [s]
      (gen/tuple (gen/return s)
                 (gen/bind
                  (gen/choose 0 (count s))
                  (fn [start]
                    (gen/one-of
                     [(gen/tuple (gen/return start))
                      (gen/tuple (gen/return start)
                                 (gen/choose start (count s)))]))))))))
(comment
  (gen/sample subs-args-gen)
;;  (["" 0] ["k" 1 1] ["" 0] ["…_" 0 0] ["" 0] ["¥/" 0] ["µ" 0] ["Þ¦\n" 3] ["" 0] ["\tsÈaíå" 1])
)
It feels a bit verbose, maybe there was a nicer way to do this?

gfredericks14:02:10

that's a good one

gfredericks14:02:36

I'm trying to think of something that shrinks better than all the gen/binds

gfredericks14:02:45

and has a good distribution

gfredericks14:02:53

those things can trade off with readability/simplicity sometimes

gfredericks14:02:56

here's a relatively simple approach

(gen/let [[s start end use-end?]
          (gen/tuple gen/string gen/nat gen/nat gen/boolean)]
  (let [start (min start (count s))
        end (min end (count s))
        [start end] (sort [start end])]
    (cond-> [s start] use-end? (conj end))))

gfredericks14:02:29

there might be edge cases there with (count s), I'm not sure how subs works

gfredericks14:02:15

and you might complain that this will be more likely than you'd want to generate args that are equal to (count s), and if that bothers you then you could do something like use (gen/choose 0 1000000) for start and end and then scale them down to the size of the string

borkdude14:02:40

@gfredericks cool, I did not know about gen/let

borkdude14:02:22

the edge cases for subs I’ve already worked out, 0 <= start <= end <= (count s)

borkdude14:02:41

(yes even end equals to (count s) works)

gfredericks14:02:42

So I got it right then I think

borkdude14:02:18

yeah, the distribution might be less nice, like you said

borkdude14:02:39

I like the boolean idea:

(gen/bind
   (gen/tuple (gen/string) (gen/boolean))
   (fn [[s end?]]
     (if end?
       (gen/bind
        (gen/choose 0 (count s))
        (fn [start]
          (gen/tuple
           (gen/return s)
           (gen/return start)
           (gen/choose start (count s)))))
       (gen/tuple
        (gen/return s)
        (gen/choose 0 (count s))))))

gfredericks14:02:47

the sorting trick is also good for generating ordered numbers w/o having to do it in stages

gfredericks14:02:39

I probably go overboard on avoiding gen/bind

gfredericks14:02:50

you can actually write this fairly simply by using multiple clauses in gen/let

gfredericks14:02:12

(which expands to gen/bind)

gfredericks14:02:01

(gen/let [s gen-string, start (gen/choose 0 (count s)), end (gen/choose start (count s)), use-end? gen/boolean] (cond-> [s start] use-end? (conj end)))

borkdude14:02:29

if gen/tuple accepted nil, then you could write:

(fn [start]
          (gen/tuple
           (gen/return s)
           (gen/return start)
           (when end? (gen/choose start (count s)))))

gfredericks14:02:48

I just have this instinct to not use gen/bind if it can be avoided, which is probably not worth the effort in a lot of cases

borkdude15:02:16

is gen/let a new thing? my REPL says it doesn’t exist

borkdude15:02:46

oh wait, gen from test.check, I think I’m using the one from spec

gfredericks15:02:46

maybe new in 0.9.0?

borkdude15:02:20

yeah:

(g/let [s g/string
          start (gen/choose 0 (count s))
          end (gen/choose start (count s))
          use-end? (gen/boolean)]
    (cond-> [s start] use-end? (conj end)))
works. awesome.

borkdude15:02:36

so g/let is the monad-ish thing in test.check

gfredericks15:02:54

yeah; it's just sugar over gen/fmap and gen/bind

borkdude15:02:09

totally useful sugar

borkdude15:02:48

this also works and avoids generating end if not needed:

(g/let [s g/string
          start (g/choose 0 (count s))
          use-end? g/boolean]
    (if use-end?
      (g/let [end (g/choose start (count s))]
        [s start end])
      [s start]))