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?that's a good one
I'm trying to think of something that shrinks better than all the gen/binds
and has a good distribution
those things can trade off with readability/simplicity sometimes
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))))
there might be edge cases there with (count s), I'm not sure how subs works
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
@gfredericks cool, I did not know about gen/let
the edge cases for subs I’ve already worked out, 0 <= start <= end <= (count s)
(yes even end equals to (count s) works)
So I got it right then I think
yeah, the distribution might be less nice, like you said
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))))))
the sorting trick is also good for generating ordered numbers w/o having to do it in stages
oh yeah.
I probably go overboard on avoiding gen/bind
you can actually write this fairly simply by using multiple clauses in gen/let
(which expands to gen/bind)
(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)))
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)))))
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
is gen/let a new thing? my REPL says it doesn’t exist
not too new
oh wait, gen from test.check, I think I’m using the one from spec
maybe new in 0.9.0?
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.so g/let is the monad-ish thing in test.check
yeah; it's just sugar over gen/fmap and gen/bind
totally useful sugar
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]))