Details
-
Type:
Enhancement
-
Status:
Open
-
Priority:
Minor
-
Resolution: Unresolved
-
Affects Version/s: None
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
Description
I think the syntax of clojure.core/for would be a good fit for test.check's combinators. For example:
(defn gen-even-subset "Returns a generator that generates an even-cardinality subset of the given elements" [elements] (gen/for [bools (apply gen/tuple (repeat (count elements) gen/boolean)) :let [true-count (->> bools (filter identity) (count))] :when (even? true-count)] (->> (map list bools elements) (filter first) (map second) (set))))
This combines the capabilities of fmap, bind, and such-that into a familiar syntax.
One downside here is the temptation to use multiple clauses for independent generators, resulting in a use of gen/bind when gen/tuple would be simpler and presumably shrink easier. An approach to this is an additional supported clause, perhaps called :parallel, that uses the syntax of :let to provide the functionality of gen/tuple:
(gen/for [:parallel [n1 gen/nat
n2 gen/nat]
:let [sum (+ n1 n2)]]
{:nums [n1 n2] :sum sum})
Compared to gen/tuple, this has the advantage of placing generators syntactically next to names, rather than segregating the generators from the names.
The :parallel feature has not been added to the current patches.
I think there might be some design ambiguity around the meaning of :when. In particular, in the following contrived example:
In my default design this can hang, for the same reason that this code can hang:
(bind nat (fn [n] (such-that (fn [v] (pos? (reduce + v))) (vector (return n)))))
But it could just as well have been written:
(such-that (fn [v] (pos? (reduce + v))) (bind nat (fn [n] (vector (return n)))))
So the issue is whether a :when filter is applied to just the previous generator or to all of the previous generators. I have some hazy notion that the latter would be less efficient in some cases, but I'm not sure what. So I think our options are:
My gut is to do option 1 and just apply :when to the previous generator.
(bind nat (fn [n] (such-that (fn [v] (pos? (reduce + v))) (vector (return n)))))
(such-that (fn [v] (pos? (reduce + v))) (bind nat (fn [n] (vector (return n)))))