adapted from CLJ-1997
A common usage of gen/let
might look something like this:
(gen/let [a gen-a
b gen-b]
(f a b))
The crucial characteristic of this code is that the generator for b
does not depend on the value a
(though in general gen/let
allows
this, just like clojure.core/let
). Because
of this independence, the ideal expansion is:
(gen/fmap
(fn [[a b]] (f a b))
(gen/tuple gen-a gen-b))
However, because gen/let
cannot, in general, tell whether or not the expression for
the generator for b
depends on a
, it needs to fallback to a more general expansion:
(gen/fmap
(fn [[a b]] (f a b))
(gen/bind
gen-a
(fn [a]
(gen/tuple (gen/return a) gen-b))))
Using gen/bind
greatly reduces shrinking power, and so it's best to avoid it when possible.
A careful user could get around this by using gen/tuple
explicitly, e.g.:
(gen/let [[a b] (gen/tuple gen-a gen-b)]
(f a b))
But this is rather less readable than normal gen/let
usage, and so not ideal.
A :parallel
clause allows the user to specify that multiple
generators are independent, so the example above becomes:
(gen/let [:parallel
[a gen-a
b gen-b]]
(f a b))
:parallel
clauses compile to gen/tuple
, and can be intermingled
with regular binding clauses:
(gen/let [x gen-x
:parallel
[a (gen-a x)
b (gen-b :foo x)]
y (gen-y a x)]
(f a b x y))
- PROs
- semantics are explicit, no magic
- generators and bindings are placed next to each other, unlike
with
gen/tuple
where the user needs to manually match them up by position
- CONs
- an extra level of nesting
- there's probably another CON
I don't think anybody will like this because it looks very foreign but I thought it was an interesting idea since a map suggests the idea of order-independence:
(gen/let {a gen-a
b gen-b}
(f a b))
This would simply be an alternative macro that always compiles to
gen/tuple
. Alex said he didn't like the idea of alternate let
s,
and I also think it would be less useful for mixing things.
test.chuck/for is a
macro very similar to gen/let
, where :parallel
was originally
implemented. It could be promoted to test.check proper, and users
could be encouraged to use for
instead of let
when they care about
shrinking efficiency.
- PROs
- Several other features too (
:let
,:when
)
- Several other features too (
- CONs
- Possibly more confusing (alex thinks so at least)
- PROs
- Keeps everything simple
- CONs
- The problem remains