Conversation
typing/parmatch.ml
Outdated
| when an "incoherence" is not detected by this check. | ||
| *) | ||
|
|
||
| let first_column_is_coherent matrix = |
There was a problem hiding this comment.
This may be a detail, but I think it would be slightly nicer to have a coherent_heads h1 h2 binary function that checks whether two discriminant heads are coherent with each other, and expose this function in the module -- I think it may be of use later. Then you can implement the coherence check with a fold_left, or with this fairly amusing trick
let coherent_column = function
| [] -> true
| r::rs ->
List.for_all2 (fun (h1, _) (h2, _) -> coherent_heads h1 h2)
(r::rs) (rs @ [r])There was a problem hiding this comment.
Your trick is cute, but:
- I think mine is slightly more efficient
- I don't think yours works: what happens if the column is
(1 _ 'a' _)?
As for the coherent_heads function, you just want my local same_kind lifted out (making its "discr_pat" a parameter)?
There is no need for it yet, I'd rather keep things as is and make the function globally available only when (if ever) we happen to need it.
There was a problem hiding this comment.
Indeed, the trick is wrong for the reason you give, the relation is not transitive. This raises a question: is it actually the case that only checking coherence of the first non-any pattern with all others is enough? Should we test coherence between all couples of patterns? Or is the relation transitive on all non-any patterns?
There was a problem hiding this comment.
is it actually the case that only checking coherence of the first non-any pattern with all others is enough?
Yes.
(the proof is left as an exercise for the careful reviewer)
typing/parmatch.ml
Outdated
| | Const_nativeint _, Const_nativeint _ | ||
| | Const_float _, Const_float _ | ||
| | Const_string _, Const_string _ -> true | ||
| | _, _ -> false |
There was a problem hiding this comment.
Could this be made non-fragile, by listing the alternatives at least in the first one?
| incompatibility: clearly this is fine. | ||
| - if we end up returning [true] then we're saying that [qs] is useful while | ||
| it is not. This is sad but not the end of the world, we're just allowing dead | ||
| code to survive. |
There was a problem hiding this comment.
I think that the correct check is not of whether pss is coherent, but whether pss followed by qs is coherent. See remarks below.
There was a problem hiding this comment.
That seems right. I changed all such places.
typing/parmatch.ml
Outdated
| satisfiable pss (simple_match_args p omega @ qs)) | ||
| constrs | ||
| else | ||
| if not (first_column_is_coherent pss) then ( |
There was a problem hiding this comment.
Here q is omega, so coherent pss is the same as coherent (q :: pss), and this test is correct -- but a comment could point that out.
There was a problem hiding this comment.
I'm starting to feel like we have enough comments that really simple ones (like this one would be) can actually be omitted (I believe that, as is, one can fairly quickly recover the reason for not checking q :: pss)
typing/parmatch.ml
Outdated
| let q0 = discr_pat q pss in | ||
| satisfiable (build_specialized_submatrix ~extend_row:(@) q0 pss) | ||
| (simple_match_args q0 q @ qs) | ||
| if not (first_column_is_coherent pss) then ( |
There was a problem hiding this comment.
Here I think that we should check first_column_is_coherent (q0 :: pss) -- that's the idea anyway, I suspect that the types don't match and that the API would have to change slightly.
typing/parmatch.ml
Outdated
| (list_satisfying_vectors | ||
| (build_specialized_submatrix ~extend_row:(@) q0 pss) | ||
| (simple_match_args q0 q @ qs)) | ||
| if not (first_column_is_coherent pss) then ( |
There was a problem hiding this comment.
again we should check also if q0 is coherent with the rest.
| matrix are stable. *) | ||
| List.fold_left (fun acc (_, { varsets; _ }) -> | ||
| List.fold_left IdSet.union acc varsets | ||
| ) IdSet.empty rs |
There was a problem hiding this comment.
I have worked on a better implementation of this on my own (a distinguished All value), as I thought that this would be outside the scope of your PR. I hope to send a PR for this soon.
There was a problem hiding this comment.
This is now pushed as 63ed2cc, please take a look, feel free to ask for changes, and eventually to integrate into the present PR if you wish to -- I can rebase my own on top of the final version of this commit.
There was a problem hiding this comment.
The idea of your implementation seems nice indeed (I haven't actually reviewed the actual code just yet), but IMO they are independent (which is a good thing) and should be reviewed and merged separately (that is, I don't want to "merge" the GPRs together before actually merging into trunk).
|
Approved in general, but see comments. |
As per Gabriel's remark on GPR ocaml#1550
typing/parmatch.ml
Outdated
| (* The handling of incoherent matrices is kept in line with | ||
| [satisfiable] *) | ||
| if not (first_column_is_coherent pss) then | ||
| if not (first_column_is_coherent ((uq, make_row rem) :: pss)) then |
There was a problem hiding this comment.
maybe we could write (all_coherent (first_column pss)) and (all_coherent (uq :: first_column pss)) to avoid having to come up with the rest of the row that we don't actually care about?
There was a problem hiding this comment.
Ah, that's what you meant by "the API might need some adjusting"!
Yes, I too find my current implementation a bit distasteful and yours seems prettier. But I'm also worried that yours means iterating over the rows once more, and allocating a fresh list with as many elements as there are rows, whereas with the current implementation we don't need to iterate and don't allocate much.
I'm tempted to keep my implementation because of these (probably unwarranted) performance concerns. But I'll welcome your input on that issue and am ready to change my mind if you feel differently.
There was a problem hiding this comment.
I do think that the performance concern is unwarranted (the compiler already allocates, this case of the function already allocates, and we have no latency constraints), but from your answers regarding matrix_stable_vars it looks like I should send a cleanup PR after this one is merged anyway, so I don't mind proposing some API changes at the same time.
There was a problem hiding this comment.
Alright, sounds good to me :)
I will leave things as is for now then.
|
Btw @gasche , can you suggest/push a Changes entry? |
|
Changes, in section "Working version > Internal/compiler-libs changes" |
|
Thanks, I added the Changes entry (but I put it in the bugfixes section). |
|
If you're not interested in (1) being perfectionist over the coherence API in this PR or (2) removing the |
ea42840 to
83a4701
Compare
|
I rebased, squashed a few of the commits together, and added a "style commit" on top doing the API changes proposed (i.e. the split of As for the N.B. I left the style commit on its own so it's easier to review, but I think it should eventually be squash into the "explicitly check coherence of first column" one. I'll be happy to squash it and merge the PR in trunk once you've read it and confirmed one last time. |
|
Yes, I think the API change makes for nicer code, thanks! One minor question: |
Indeed just "simplified" patterns, as documented. |
|
Feel free to merge. |
Also extends the testsuite.
83a4701 to
9185e77
Compare
|
Thanks, I squashed the style commit and merged. |
|
We may need to get this in 4.06 anyway, see MPR#7713. |
| | Tpat_constant(c1), Tpat_constant(c2) -> const_compare c1 c2 = 0 | ||
| | Tpat_tuple _, Tpat_tuple _ -> true | ||
| | Tpat_lazy _, Tpat_lazy _ -> true | ||
| | Tpat_record _ , Tpat_record _ -> true |
There was a problem hiding this comment.
Note: we could check Array.length lbl1.lbl_all = Array.length lbl2.lbl_all here as you do for the coherence check (or some richer check in general). (Currently the tuples are checked but not the records.) If one of the two pattern is empty (no label), they may match.
| end | ||
| | Tpat_tuple l1, Tpat_tuple l2 -> List.length l1 = List.length l2 | ||
| | Tpat_record ((_, lbl1, _) :: _, _), Tpat_record ((_, lbl2, _) :: _, _) -> | ||
| Array.length lbl1.lbl_all = Array.length lbl2.lbl_all |
There was a problem hiding this comment.
By the way, why are we checking the length only and not the lbl_all value itself?
| Array.length lbl1.lbl_all = Array.length lbl2.lbl_all | ||
| | Tpat_any, _ | ||
| | _, Tpat_any | ||
| | Tpat_record ([], _), Tpat_record ([], _) |
There was a problem hiding this comment.
Actually I think that this line is buggy: an empty record pattern is coherent with any other record pattern, not just empty ones.
|
The check could be made stricter in various places, and it would probably be better. It wasn't done because it wasn't "necessary". There is a lot of room for improvement. For the empty record pattern, I disagree with you. |
| (* only omegas on the column: the column is coherent. *) | ||
| true | ||
| | discr_pat -> | ||
| List.for_all (coherent_heads discr_pat) column |
There was a problem hiding this comment.
Note: this line is wrong if we accept the empty record pattern at any record type (because then the relation is not transitive anymore).
|
We did another review of the PR with @maranget, with the merge to the 4.06 branch in mind. We have carefully reviewed and believe that the PR is correct. The review turned out two things:
|
|
@trefis, would you consider submitting a rebased version of this PR against the 4.06 branch? |
Will do. |
|
I'm not sure which way is best, but it may be necessary to try to rebase to see how well it goes before making a decision... |
cherry-picked from b573eaa (ocaml#1550)
cherry-picked from f5619dc (ocaml#1550)
cherry-picked from ad91c46 (ocaml#1550)
Also extends the testsuite. originally cherry-picked from fa5a4bb (ocaml#1550), with a careful rebase by Thomas Refis to account for the fact that the refactoring GPR#1448, which the original patch relied on, is not present in the 4.06 branch.
Co-authored-by: Paul-Elliot <peada@free.fr> Co-authored-by: Christine Rose <christinerose@users.noreply.github.com>
Spin off of #1518 following discussions with Gabriel and Luc.
This version of the patch introduce an explicit coherence check at various points in parmatch.
There is a somewhat lengthy description of the check in the code itself, so I won't go into the details here (improvements to that documentation are very welcome though!).
A perhaps more readable version of the diff (as it ignores whitespaces changes) on
parmatch.mlcan be seen here.A note on pattern typing
Currently patterns are typechecked from "left to right", that is
is well typed, but the following is not:
From the point of view of typing alone, one could imagine lifting that restriction at some point in the future and allowing both versions to typecheck.
In pratice however, in their current state, neither parmatch nor matching would survive such a change.
Incidentally
Parmatch.every_satisfiablewas reversing the order of columns between being called and callingsatisfiable.You'll notice the
List.rev, hidden in the commit introducing the coherence check, fixing that problem.Limitations of the current coherence check
In various places I give a brief explanation of what can happen when we accept a column as coherent while it is in fact ill-typed.
The potential outcomes include false positives and negatives.
However: