stedolan / crowbar

Property fuzzing for OCaml
MIT License
180 stars 31 forks source link

bind (or join or similar) combinator #44

Closed raphael-proust closed 6 years ago

raphael-proust commented 6 years ago

In order to test an encoding library (à la json-typed) I'm having to generate values for a recursive type with some specific restrictions. Here's a simplified version:

type 'a e =
  | Ranged_int: (int * int) -> int e
  | List: 'a t -> 'a list e
  | Tuple: 'a t * 'b t -> ('a * 'b) t
  (* other constructors omitted *)

This type describes ways to encode/decode values and can be passed to the following functions:

val constr: 'a e -> 'a -> json
val destr: 'a e -> json -> 'a result

Note that in int list t, all integers must share the same range. And similarly, in int list list t, int list list list t, etc. However, in a tuple, each side can be of different rangedness.

This lends itself nicely to a generator such as:

let rec gengen: ('a e * 'a gen) gen = fix (fun gen ->
  choose [
    map [int; int] (fun min max -> Ranged_int (min, max), range ~min (max - min));
    map [gen] (fun (e, g) -> List e, list g);
    map [gen; gen] (fun (e1, g1) (e2, g2) -> Tuple (e1, e2), map [g1, g2] (fun v1 v2 -> (v1, v2)));
  ])

Which needs some form of bind to extricate the nested gen.

let gen =
    bind gengen (fun (e, g) ->
    bind g (fun v ->
    return (e, v)
))

Is this doable with the current interface? I don't think it is, but maybe I missed some combinator trickery. Is it possible to implement?


There are alternatives:

samoht commented 6 years ago

Related to #36 I guess

raphael-proust commented 6 years ago

Yep, pretty much the same thing. I'll just close that issue to avoid duplicate discussion and such.