test-check

borkdude 2019-02-18T09:36:34.011800Z

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?

2019-02-18T14:21:10.012100Z

that's a good one

2019-02-18T14:21:36.012400Z

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

2019-02-18T14:22:45.012600Z

and has a good distribution

2019-02-18T14:22:53.013Z

those things can trade off with readability/simplicity sometimes

2019-02-18T14:25:56.014800Z

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))))

2019-02-18T14:26:29.015600Z

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

2019-02-18T14:27:15.016600Z

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

2019-02-18T14:27:49.016800Z

@borkdude

borkdude 2019-02-18T14:34:40.017200Z

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

borkdude 2019-02-18T14:35:22.017800Z

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

borkdude 2019-02-18T14:35:41.018200Z

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

2019-02-18T14:36:42.018800Z

So I got it right then I think

borkdude 2019-02-18T14:37:18.019100Z

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

borkdude 2019-02-18T14:52:39.019500Z

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))))))

2019-02-18T14:54:47.020100Z

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

borkdude 2019-02-18T14:55:33.020300Z

oh yeah.

2019-02-18T14:56:39.020600Z

I probably go overboard on avoiding gen/bind

2019-02-18T14:56:50.021100Z

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

2019-02-18T14:57:12.021500Z

(which expands to gen/bind)

2019-02-18T14:58:01.022400Z

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

borkdude 2019-02-18T14:58:29.023Z

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)))))

2019-02-18T14:58:48.023200Z

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

borkdude 2019-02-18T15:00:16.023500Z

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

2019-02-18T15:00:41.024200Z

not too new

borkdude 2019-02-18T15:00:46.024700Z

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

2019-02-18T15:00:46.024800Z

maybe new in 0.9.0?

borkdude 2019-02-18T15:02:20.025100Z

yeah:

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

borkdude 2019-02-18T15:02:36.025500Z

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

2019-02-18T15:02:54.025900Z

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

borkdude 2019-02-18T15:04:09.026100Z

totally useful sugar

borkdude 2019-02-18T15:09:48.026500Z

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]))

2019-02-18T15:10:37.026700Z

Yep