New warning: warn on generic comparison#1642
Conversation
|
The fact that you failed to compile the compiler with the new warning enabled is not exactly encouraging in term of usability, and the various code rewrite, duplication or even straigthforward deletion(!) in this PR drives this point further. Have you any plan to improve the usability of this warning (by adding comparison combinators for instance)? |
Let me clarify: The compiler compiles fine (when using only the first two commits). The warning works as advertised, and only triggers on compiling stdlib, tools and ocamldoc. My experience was actually quite the opposite of your observation. Most uses the warning triggered on were easy to fix by constraining the type and therefore speeding up the compiled code.
Yes, I do. The warning needs to support disabling by annotations like |
|
I think the warning would make a lot of sense if we could provide a coherent story to actually avoid the generic comparison (perhaps with modular implicits). It is nice to detect "direct" use of the comparison, but the warning (in its current implementation strategy at least) would not allow to detect indirect uses through library functions such as List.assoc, Hashtbl.find, etc. Also, even for basic types, the stdlib would need to expose explicit comparison functions for those types (like the new Float.compare from the PR about the new Float module -- we would need that for all basic types). I don't think it is a particularly nice style to accept the generic comparison, but only in cases where the optimizer is clever enough to detect it applies on known basic types. |
|
One way to avoid the warning it is by type constraints. |
Drup
left a comment
There was a problem hiding this comment.
I think having the warning is useful. It might be enabled locally in some files to avoid the generic comparison (debugging where it is used is extremely annoying). Sure, it doesn't prevent external libraries to use it, but I do not think that's so bad, especially for alternate stdlibs.
I really don't think we should try to apply it to the ocaml compiler itself though. At least not for now. So I propose we drop the second commit.
The implementation looks fine, but it's really missing some tests, in particular to exercise the locality of the attribute (checking that e1; e2[@warning ... only warns in e1, not 2).
bf46672 to
8b64907
Compare
I rebased on current trunk and added a test, which currently fails because disabling the warning using attributes doesn't work at all. So now still missing is:
I can do the first two, but I am stuck regarding the
So now I'm not even sure whether it is supposed to work with the new warning... |
|
Yes, it is supposed to work. You should look at |
0914bed to
b42e6f9
Compare
|
Ok, I added enough calls to |
25db463 to
473a21c
Compare
|
@alainfrisch Do you still think we should have a better stdlib story before adding this warning? Given that you're probably the one most familiar with that part of the codebase, what do you think of the implementation? |
|
Yes, I'm still not a big fan of relying on specialization of primitives to allow uses of the generic comparison at specific types. The direction should be to provide type-specific comparisons functions in Stdlib (and longer term, perhaps rely on modular implicits) and warn on any use of the generic comparison, directly or indirectly. This could be implemented using #1804 (one would annotate the generic comparison functions, but also List.assoc, List.find, Hashtbl functions, etc). In particular, this PR could give a false sense of security, as it will not detect indirect uses of the generic comparison. That said:
So I'm not against merging this PR. I only quickly looked at the implementation and it looks ok, but I haven't done a more thorough review yet. @Drup : if you feel confident in the implementation, I can merge the PR. |
a92ffaa to
e519652
Compare
|
I'd love to see this merged soon because rebasing it again and again is a rather boring job. The actual implementation is quite simple and straightforward. |
bytecomp/translmod.ml
Outdated
| transl_structure loc fields cc rootpath final_env rem | ||
| | Tstr_attribute attr -> | ||
| Builtin_attributes.warning_scope ~ppwarning:false [attr] | ||
| (fun () -> transl_structure loc fields cc rootpath final_env rem) |
There was a problem hiding this comment.
It would be better to useBuiltin_attributes.warning_attribute here (in line with what Typemod does).
Thinking more about it, I think this needs to be discussed. The approach in this PR is to try replicating the propagation of the warning-control attribute as done during type-checking. But there are 47 calls to It would be more robust and future proof to process the attributes only once and store the resulting warning settings, either with new fields in the Typedtree (eg. |
e519652 to
b4a5866
Compare
|
Thanks for your review. I changed two usages of |
Changes
Outdated
| ### Language features: | ||
|
|
||
| - Add optional warning for (possibly unwanted) usage of the generic comparison | ||
| function. |
There was a problem hiding this comment.
Less verbose: "Add warning for use of generic comparison functions."
utils/warnings.ml
Outdated
| | Unsafe_without_parsing (* 64 *) | ||
| | Redefining_unit of string (* 65 *) | ||
| | Unused_open_bang of string (* 66 *) | ||
| | Generic_comparison (* 67 *) |
There was a problem hiding this comment.
I think it would be nicer to include the offending operation in question, perhaps like this:
| Generic_comparison of stringand then give more precise warnings:
this comparison will use the generic equality function
this comparison will use the generic comparison function
this comparison will use the generic inequality function
rather than calling everything "comparison". (For one thing, it's not quite accurate to say "the" generic comparison function, but I think it's generally better to be as precise as possible, if it's not a lot of extra work.)
utils/warnings.ml
Outdated
| Hint: Did you mean 'type %s = unit'?" name | ||
| | Generic_comparison -> | ||
| "this comparison will use the generic comparison function.\n\ | ||
| It may have unexpected semantics in some cases." |
There was a problem hiding this comment.
Rather than the rather vague "It may have unexpected semantics in some cases" (which might be true of anything, depending on your expectations) I think it would be better to say what the unexpected semantics are. Perhaps "which operates on the low-level representations of values, ignoring types".
the warning is triggered when a structural comparison is not optimized to int/float/string comparison, but the generic comparison function is used.
b4a5866 to
e613b60
Compare
|
Thanks @yallop for your review. I changed the pull request according to your proposals. |
|
In the future I think that it could be interesting to teach an implementation of this warning that most builtin parametrized types preserve comparability: Another thing I would like to see is a more complete documentation of the rationale for this warning in the Warnings Reference. The important point is that some datatypes (such as Set.t and Map.t) have a non-canonical representations for which the structural equality is too fine-grained, not the equality the user actually wants. This is too long to explain in a warning and possibly in warn-help, but it should be in the manual. Again, this can come in a later PR. |
Another point worth noting is that the structural equality doesn't fare well with "functional" values: |
|
I'm not familiar enough with translcore and translmod to be sure from the code, but what is the expected behavior for use of polymorphic compare on types whose representations are not exposed fully in the interface? That is, any type whose representation is hidden by an interface, or exposed but |
|
Recent remarks by @jberdine and @gasche suggest that it might be better to check whether type is "ok for generic comparison" rather than "the primitive specialization applies". What about marking some type variables (in the value declaration for generic comparison, but also possibly for |
|
I thought at first that a completely different implementation would be required to check that a type is "ok for comparison", but I think that it is actually reasonable to do it as a small change over this PR: at the place in Translprim.specialize_primitive where this PR raises a warning, we can condition the warning on a type-directed check that the type is indeed not properly comparable. Specific types corresponding to static specialization opportunities have already been ruled out, but we can there have a richer check that knows about comparability of types constructed using (non-private) lists, arrays, options etc. You are proposing a more ambitious change, which is basically to re-introduce SML's "equality types" in the type theory of OCaml. We could do this, but I would rather favor a low-tech option (as in this PR, plus maybe a more clever check), and wait for some sort of type-classes to have a flexible long-term solution. |
|
This PR was inspired by the containers library which made the comparison operators monomorphic. What we are trying to do here is find some kind of compromise between dogmatic monomorphism and complete polymorphism.
|
|
I'm a bit sad to not see progress here. As I understand it, people are mostly in favor. @madroach, do you still want to push this through ? |
|
Honestly, my motivation is limited. I could of course do a merge again. But I won't do any work on the code before consensus is reached on when the warning should trigger and my implementation is the way to go. @gasche wrote:
I tried to explain my rationale about when to warn in my last comment, but let me give an example in addition. Even on integers the physical comparison may not yield the expected result if you are working on a residue system. |
|
Don't let my comments block this PR. If a maintainer is happy with the proposal and willing to review the PR, I've no opposition to merging. I still don't believe this is the right approach, but I don't have any better ready to propose, and the warning might be better than nothing, at least for some users, and others can always disable it. (But please don't mention me in the list of reviewers, I haven't looked at the code!) |
|
I think it would be better if this was an alert rather than a warning. Then we'll be able to annotate stdlib functions like We can always add eqtypes to the type system later if we ever want to. |
|
x-ref : #9928 |
|
Closing, as the discussion has moved to #9928. |
…caml#1642) --------- Co-authored-by: Sabine Schmaltz <sabineschmaltz@gmail.com>
Add a new warning 63 which will trigger when the compiler fails to specialize a comparison primitive and will use the generic comparison function.
This is not finished yet. Still missing is:
[@warning -63]work, which it does not at the moment[@warning -63]help is appreciatet as I am stuck at this point.