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/tuplewhere 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 lets,
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