Add QCheck2.Gen.recursive - #420
tmcgilchrist wants to merge 1 commit into
Conversation
Build a generator for a recursive type from its base and recursive cases, combined with oneof or oneof_weighted. Each recursive case is handed a generator for the same type at a smaller size. The size is multiplied by the reciprocal of the golden ratio (~0.618) per level rather than halved, so the expected depth grows with the drawn size instead of following a fixed division schedule. Recursion stops once the size reaches 1, and the optional ?scale argument is clamped to [0, n - 1] so a schedule that fails to decrease cannot make generation diverge.
jmid
left a comment
There was a problem hiding this comment.
Thanks Tim, this does seem like a handy short-hand combinator! 😃👍
For context, I've previously lined up to "a big QCheck 1.0" by aligning combinator names across QCheck.Gen, QCheck.arbitrary, and QCheck2.Gen (see PRs #367 to #396). As such I'd very much like to see something like this in the other APIs for consistency.
As a short-hand I initially thought that recursive may be overly general, thinking that
recursive(specialized tooneof) andrecursive_weighted(specialized tooneof_weighted)
may be worth considering, since these two would be the primary use cases AFAICS (do you have a use case for the more generalized interface?).
This would make the usage even shorter:
let tree_gen =
QCheck2.Gen.(recursive
[ map (fun i -> Leaf i) nat ]
[ (fun self -> map2 (fun l r -> Node (l, r)) self self) ])Furthermore, the split x and x_weighted would fit reasonably with how we've (re)named the other library combinators going forward. WDYT?
Having a second look at the current proposed signature:
val recursive :
?scale:(int -> int) ->
('a t list -> 'a t) ->
'a t list ->
('a t -> 'a t) list ->
'a t
(** [recursive combine base rec_cases] builds a generator for a recursive
type out of its non-recursive cases [base] and its recursive cases
[rec_cases], combined with [combine] - typically {!oneof}, or
{!oneof_weighted} composed with a weighting function.
[...] *)I can't see how to instantiate this to using oneof_weighted (composed with a weighting function). Can you share an example? (or even better: a test!) 🤔
Build a generator for a recursive type from its base and recursive cases, combined with oneof or oneof_weighted. Each recursive case is handed a generator for the same type at a smaller size.
The size is multiplied by the reciprocal of the golden ratio (~0.618) per level rather than halved, so the expected depth grows with the drawn size instead of following a fixed division schedule. Recursion stops once the size reaches 1, and the optional ?scale argument is clamped to [0, n - 1] so a schedule that fails to decrease cannot make generation diverge.
I had this convenience function defined in a project where I needed to write a number of recursive generators.
Before:
After: