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/bind
s
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]))
Yep