Skip to content

Deriver: share fuel between mutual generators - #195

Merged
jmid merged 10 commits into
c-cube:masterfrom
vch9:fix188
Dec 13, 2021
Merged

Deriver: share fuel between mutual generators#195
jmid merged 10 commits into
c-cube:masterfrom
vch9:fix188

Conversation

@vch9

@vch9vch9 commented Oct 14, 2021

Copy link
Copy Markdown
Contributor

Fixes#188

Now mutual recursive generators share the size fuel (or indicator w/e).

openQCheck.Genletrec gen_tree_sizedn=
map
(fungen0 -> Node gen0)
(map
(fun (gen0, gen1) -> (gen0, gen1))
(pair int (gen_forest (n /2))))))]
andgen_forest_sizedn=match n with|0 -> pure Nil|n ->
frequency
[(1, pure Nil);
(1,
(map (fungen0 -> Cons gen0)
(map (fun (gen0, gen1) -> (gen0, gen1))
(pair (gen_tree_sized (n /2)) (gen_forest_sized (n /2))))))]))
let gen_tree = sized @@ gen_tree
let gen_forest = sized @@ gen_forest

Are you ok with that fix @jmid ?

Also in ba96a95 I removed the use of frequency when the list contains one element only.

Comment threadsrc/ppx_deriving_qcheck/README.md Outdated
@jmid

jmid commented Oct 24, 2021

Copy link
Copy Markdown
Collaborator

Great, thanks! 🙂 👍

Is it correctly understood that

  • variant types derive "unsized" _gen generators based on oneof
  • recursive variant types derive sized _gen generators prefixed with a Gen.sized @@
  • mutually recursive variant types derive a pair of generators (one _sized and one _gen with the latter calling the former with Gen.sized @@)?

If so, would it make sense to unify the last two, so that any kind of recursive variant needing fuel (mutually recursive or not) always derives a pair of generators _sized and _gen?

  • it would be more predictable
  • this would mean one less special case to understand/consider/remember - both for developers and for end-users
  • it would allow more flexibility if one wants to pass a different fuel generator with Gen.sized_size than the default one that Gen.sized uses

@vch9

vch9 commented Oct 25, 2021

Copy link
Copy Markdown
ContributorAuthor
  • recursive variant types derive sized _gen generators prefixed with a Gen.sized @@
  • mutually recursive variant types derive a pair of generators (one _sized and one _gen with the latter calling the former with Gen.sized @@)?

yes

If so, would it make sense to unify the last two, so that any kind of recursive variant needing fuel (mutually recursive or not) always derives a pair of generators _sized and _gen?

Yes it is! I did not want to break to much code regarding this particular fix. However, if we agree on the fix I can do this as well :) I will ping you when it's done.

@vch9

vch9 commented Oct 25, 2021

Copy link
Copy Markdown
ContributorAuthor

Done @jmid


The rule is now:

  • If the type t is recursive, you have:
    • a sized generator: gen_sized : n -> t Gen.t
    • a generator (using Gen.sized): gen : t Gen.t`
  • If the the type t is mutually recursive, for each t' in t, you have:
    • a sized generator (even if the t' is not recursive)
    • a generator (using Gen.sized) for t'

The idea behind the second point is: share the size indicator in each sub-type.


There was also a fix in these commits:

type'a tree = Leafof'aNodeof'atree*'atreetypeint_tree = inttree(* generator for int_tree before the fix: *)let gen_int_tree = gen_tree
(* generator for int_tree after the fix: *)letgen_int_treegen_treeQCheck.Gen.int

The parameters were ignored.

@jmid

jmid commented Dec 4, 2021

Copy link
Copy Markdown
Collaborator

Could I ask you to rebase? (it would be nice to have a green light from CI before merging, now that it's working again) 🙏‍

@vch9vch9 changed the title Deriver: share fuel between mutual generatorsWIP: Deriver: share fuel between mutual generatorsDec 8, 2021
@vch9
vch9force-pushed the fix188 branch 2 times, most recently from 93c9f02 to d0c0b1eCompareDecember 8, 2021 15:48
@vch9vch9 changed the title WIP: Deriver: share fuel between mutual generatorsDeriver: share fuel between mutual generatorsDec 8, 2021
@vch9

vch9 commented Dec 8, 2021

Copy link
Copy Markdown
ContributorAuthor

If the CI pass and you agree on the generated code, I think we can merge this @jmid.
However, I'm not fully confident with the code I wrote, I will soon write unit tests (and/or PBTs) to test generated generators.

@vch9

vch9 commented Dec 8, 2021

Copy link
Copy Markdown
ContributorAuthor

If the CI pass and you agree on the generated code, I think we can merge this @jmid. However, I'm not fully confident with the code I wrote, I will soon write unit tests (and/or PBTs) to test generated generators.

I quickly found bugs writing these tests. They are not yet related to this specific PR I let you decide whether I should add those tests in this PR, or if we merge and I come up with a follow-up PR.

(I first wrote the deriver for QCheck.arbitrary, I then switched to QCheck.Gen.t, some generators are named differently and I did not pay attention to this)

@jmid

jmid commented Dec 8, 2021

Copy link
Copy Markdown
Collaborator

I quickly found bugs writing these tests. They are not yet related to this specific PR I let you decide whether I should add those tests in this PR, or if we merge and I come up with a follow-up PR.

(I first wrote the deriver for QCheck.arbitrary, I then switched to QCheck.Gen.t, some generators are named differently and I did not pay attention to this)

OK, makes sense. It probably makes most sense to submit those tests (and fixes) as a separate PR.

First: Sorry for leaving this hanging for some time... 😬

I took a quick look and found that there's now two copies of (what looks like) identical copies of is_type_rec:

letrec is_rec_typtyp_name=function
|{ ptyp_desc = Ptyp_constr ({ txt = x; _ }, _); _ } ->
longident_to_str x = typ_name
|{ ptyp_desc = Ptyp_tuplexs; _ } -> List.exists (is_rec_typ typ_name) xs
|_ -> false

letrec is_rec_typtyp_name=function
|{ ptyp_desc = Ptyp_constr ({ txt = x; _ }, _); _ } ->
longident_to_str x = typ_name
|{ ptyp_desc = Ptyp_tuplexs; _ } -> List.exists (is_rec_typ typ_name) xs
|_ -> false

It indicates that it would be worth it to take an extra clean-up pass over the code.

Unrelated: The comments with n / 1 look like typos to me:
https://github.com/vch9/qcheck/blob/d0c0b1eea65cd68acde284928d47597aab2d85a4/src/ppx_deriving_qcheck/ppx_deriving_qcheck.ml#L25

I was a bit confused by the order of the code: First some basic helpers, some sized_gen, some tuple+record, ... then a group of gen_from_ where sized_gen is used almost at the end. It could be nice to restructure it a bit to help the reader and add a few comments (e.g., about how you break all tuples down to 2-3-4 combinations).

There's already a tests for recursive types (tree, expr), for two-level (expr-value), and truly mutually recursive types (tree-forest), so you've done a great effort there already. If you feel like putting the plugin to the test, I just remembered this one
https://github.com/yomimono/ocaml-test-omp/blob/d086037027537ba4e23ce027766187979c85aa3d/test/parsetree_405.ml
which I think could make a solid stress test of the mutual recursion code! 😬 😉

Finally: I'm super happy with this deriving plugin (I started using it myself to avoid writing this boilerplate) - so thanks for your effort! 🙏‍

@c-cube

Copy link
Copy Markdown
Owner

I haven't looked, but thank you @vch9 for your hard work, and @jmid for the reviewing!

question: does this use ppxlib's mechanism in such a way that [@@deriving_inline ] works? That'd also be super cool :)

@vch9

vch9 commented Dec 9, 2021

Copy link
Copy Markdown
ContributorAuthor

I took a quick look and found that there's now two copies of (what looks like) identical copies of is_type_rec:

It does not surprise me, my rebase was suspicious.

It indicates that it would be worth it to take an extra clean-up pass over the code.

and, yes!

I was a bit confused by the order of the code: First some basic helpers, some sized_gen, some tuple+record, ... then a group of gen_from_ where sized_gen is used almost at the end. It could be nice to restructure it a bit to help the reader and add a few comments (e.g., about how you break all tuples down to 2-3-4 combinations).

Yes it is not ideal, I will restructure the code before this MR's changes, with more documentation obviously :). Even though, the commits in this MR are not really great: I introduced a signifcant change in d1edc6f, which I generalize in a7cb9b6 and lead to several removals from previous commit. I think with the testing suite we have for the deriver, it could be a good thing to restart from scratch to produce a more sustainable code (I had trouble to read my own code during the rebase).

If you feel like putting the plugin to the test, I just remembered this one
https://github.com/yomimono/ocaml-test-omp/blob/d086037027537ba4e23ce027766187979c85aa3d/test/parsetree_405.ml
which I think could make a solid stress test of the mutual recursion code! grimacing wink

I'll have a look, sounds like a solid test

@vch9

vch9 commented Dec 9, 2021

Copy link
Copy Markdown
ContributorAuthor

question: does this use ppxlib's mechanism in such a way that [@@deriving_inline ] works? That'd also be super cool :)

I have not used [@@deriving_inline] yet in my life, but this is something I would drastically need, I'll have a look.

@vch9
vch9force-pushed the fix188 branch 2 times, most recently from 69aa567 to e5823acCompareDecember 9, 2021 17:20
@vch9

vch9 commented Dec 9, 2021

Copy link
Copy Markdown
ContributorAuthor

@jmid This should be ready for a second round.

I introduced several commits to move/comments code which improve I believe the readability (I'm open to other suggestions as well). In particular src/ppx_deriving_qcheck/qcheck_generators.ml should facilitate #190.

Comment threadsrc/ppx_deriving_qcheck/args.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
@vch9

vch9 commented Dec 10, 2021

Copy link
Copy Markdown
ContributorAuthor

The imperative can easily be removed I believe, I thought using side effects of our loved language would simplify the code. This can be changed in this PR, or whenever this cause an issue. The environment is calculated at the beginning and can be passed as a parameters in every gen_* functions rather than using an imperative reference.

I promised myself not to insist on this - merely only point it out sweat_smile The curr_type () relies on the underlying ref option not being None though (which is how every Null-pointer bug in C/Java/... starts). @c-cube What do you think?

what's the context? if it's global mutable state then it's kind of bad, indeed. Is it for mutually recursive functions?

It was a global mutable state to remember recursive functions and facilitate at several locations whether the type was recursive or not. However, I removed the global state for a variable moved around in f6d55c6.

@vch9

vch9 commented Dec 10, 2021

Copy link
Copy Markdown
ContributorAuthor

Remove the (kind ppx_rewriter) and added ppx_deriving as a dependency.

Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
let gens = List.map (gen_from_type_declaration ~loc ~env) xs in
mutually_recursive_gens ~loc gens
let typ_names = List.map (fun x -> x.ptype_name.txt) xs in
let env = { curr_type = ""; rec_types = []; curr_types = typ_names } in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curr_type = "" surprised me a bit at first.

In a sense it moves the "absence of curr_type representation from None to "".

However, we no longer have a ref - and here the absence is only local to derive_gen.
Any reader can thus convince him/her/themself that curr_type will be updated to be
non-empty just a few lines below (l.465) before being used.

I therefore think it is fine 👍‍ (and definitely better than the ref!)

Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
@jmid

jmid commented Dec 10, 2021

Copy link
Copy Markdown
Collaborator

@jmid you should be happy with f6d55c6! I removed references, this lead to the removal of several functions that could have been inlined :p

I know this is intended as a tongue-in-cheek remark 😅 ...but just to be clear:
I am all for helper functions ... when they help readability of the code.
I suppose I also prefer that they match entities in the domain (here: longindent, env, basic types, type constructors, ...)

Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
@jmid

jmid commented Dec 10, 2021

Copy link
Copy Markdown
Collaborator

Just to repeat: I think the outcome of these iterations is clearer, simpler, and shorter.
Thanks again for a great effort! 🙏‍

@vch9

vch9 commented Dec 13, 2021

Copy link
Copy Markdown
ContributorAuthor

Just to repeat: I think the outcome of these iterations is clearer, simpler, and shorter.

I also believe that, do not worry :)

Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
Comment threadsrc/ppx_deriving_qcheck/ppx_deriving_qcheck.ml Outdated
@jmid

jmid commented Dec 13, 2021

Copy link
Copy Markdown
Collaborator

Great. I think this shaped out very nicely - thanks for your effort! 😀

I'll let the CI go green before merging (which also gives @c-cube a brief window to comment/object/celebrate...)

@vch9

vch9 commented Dec 13, 2021

Copy link
Copy Markdown
ContributorAuthor

Rebased the history - thanks a lot for the review!

@vch9

vch9 commented Dec 13, 2021

Copy link
Copy Markdown
ContributorAuthor

If the CI pass and you agree on the generated code, I think we can merge this @jmid. However, I'm not fully confident with the code I wrote, I will soon write unit tests (and/or PBTs) to test generated generators.

I quickly found bugs writing these tests. They are not yet related to this specific PR I let you decide whether I should add those tests in this PR, or if we merge and I come up with a follow-up PR.

(I first wrote the deriver for QCheck.arbitrary, I then switched to QCheck.Gen.t, some generators are named differently and I did not pay attention to this)

Follow-up PR with those tests: #208

@jmid
jmid merged commit 57c9ba7 into c-cube:masterDec 13, 2021
@vch9vch9 mentioned this pull request Dec 13, 2021
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deriver: share fuel between mutual recursive types

3 participants

@vch9@jmid@c-cube