forbid optional arguments reordering with -nolabels#9411
forbid optional arguments reordering with -nolabels#9411trefis merged 20 commits intoocaml:trunkfrom
Conversation
930166e to
7543050
Compare
|
It seems I had misunderstood what the current implementation does when As it turns, this is not the case: optional arguments can be reordered, but only them. That is: you can swap an arbitrary number of adjacent optional parameters, as long as nothing else gets reorder. let () = f ~x:() ~y:() ~z:() () ~a:() ~b:();; (* 1 *)
let () = f ~y:() ~x:() ~z:() () ~a:() ~b:();;(* 2 *)
let () = f ~x:() ~z:() ~y:() () ~a:() ~b:();; (* 3 *)
let () = f ~x:() ~y:() ~z:() () ~b:() ~a:();; (* 4 *)But these, are not: let () = f ~x:() ~y:() () ~z:() ~a:() ~b:();; (* 5 *)
(* ^^
Error: The function applied to this argument has type
?z:'a -> unit -> ?a:'b -> ?b:'c -> unit
This argument cannot be applied without label
*)
let () = f ~x:() ~y:() ~a:() () ~z:() ~b:();; (* 6 *)
(* ^^
Error: The function applied to this argument has type
?z:'a -> unit -> ?a:'b -> ?b:'c -> unit
This argument cannot be applied with label ~a
*)
let () = f ~x:() ~y:() ~z:() ~a:() () ~b:();; (* 7 *)
(* ^^
Error: The function applied to this argument has type
unit -> ?a:'a -> ?b:'b -> unit
This argument cannot be applied with label ~a
*)N.B. this is with 4.10. The present PR partially breaks this behavior, it forbids optional arguments followed by a non-optional argument to be reordered. let () = f ~y:() ~x:() ~z:() () ~a:() ~b:();;(* 2 *)
let () = f ~x:() ~z:() ~y:() () ~a:() ~b:();;(* 3 *)
(* ^^
Error: The function applied to this argument has type
?x:'a -> ?y:'b -> ?z:'c -> unit -> ?a:'d -> ?b:'e -> unit
This argument cannot be applied without label)
*)(furthermore, the error message is worse than it could be, but that should be fixable without too much trouble) My intuition is that this a bug of the current implementation, and that it should be changed. To either always allow reordering arguments given with a label, or always forbid it. @garrigue: what do you think? |
|
Could I suggest a potentially simpler solution -- get rid of |
Isn't it used to compile the labeled modules in the |
|
I think it only saves us from having some files that looks like: ...
let foo ~x ~y z = foo x y z
...that seems lower maintenance than |
|
I agree that almost nobody uses it nowadays, which actually means that people are comfortable with the current flexible label behaviour. That's good. Sorry not review your PR today, because I have a headache. But tell me when you want me to look at it. |
|
Yet another option could be to keep the |
85ac2f6 to
ab39d5d
Compare
|
I was indeed wrong in assuming this was a bug, the implementation does match what is described in the paper, which I believe can be sumed up as follows: When
This last point, which surprised me before, is an historical choice: it makes it a lot more convenient to use libraries like lablgtk where functions have a lot of optional arguments. @garrigue and I talked offline, and I'd like to propose a change to the behavior of I have now updated this PR to:
1: grepping the source of all opam packages showed only a handful of packages building with |
|
I reviewed the code. I believe it is simpler and correct (assuming we want the change in the behavior of optional labels in the Success of the tests increases my confidence :). |
Ping @c-cube btw, the diff is the following and can be applied now, regardless of whether this PR gets merged or not: diff --git a/src/gen.ml b/src/gen.ml
index fbf6b0f..2540fd6 100644
--- a/src/gen.ml
+++ b/src/gen.ml
@@ -1902,7 +1902,7 @@ module Restart = struct
match !cached with
| Some mlist -> GenMList.to_gen mlist
| None ->
- let mlist = GenMList.of_gen_lazy ?caching ?max_chunk_size g in
+ let mlist = GenMList.of_gen_lazy ?max_chunk_size ?caching g in
cached := Some mlist;
GenMList.to_gen mlist
end
@@ -1936,7 +1936,7 @@ let persistent gen =
*)
let persistent_lazy ?caching ?max_chunk_size gen =
- let l = GenMList.of_gen_lazy ?caching ?max_chunk_size gen in
+ let l = GenMList.of_gen_lazy ?max_chunk_size ?caching gen in
fun () -> GenMList.to_gen l
(*$T |
|
What about @garrigue's proposal of simplifying While we are at it, I was always a bit puzzled by the fact that it is allowed to drop all labelled arguments on a full application (in particular, I find it confusing that for a |
In practice probably not much more than my proposal (there really aren't that many users of
I have no idea. I have definitely seen it used in various random places. We would have to build all of opam to check.
I think it should. |
|
I'm in favor of doing first the minimal simple change that you have already implemented and has been approved. Is there something left to do before merging? |
|
On the implementation side no, I think we're good. In terms of approval I would like @garrigue to confirm that this is reasonable and matches what we discussed. |
|
One last check: what is the error message in Also, the paper cited above is basically the only reference which precisely explains how labels work in ocaml. It was already not 100% accurate because of the full-application rule which was added later, but this change requires another patch. High time to revise it, and put it somewhere visible enough. All in all, I'm not sure rushing this in 4.11 is a good idea. On the other hand, I completely agree that this is a really minor change, which is not going to break anything serious. I'll write a review after checking the code side-by-side. |
I tend to agree with you, as this gives a much cleaner semantics. However, we're not going to discuss this in this PR. You're free to open a new PR for that, as this is an important question. |
|
Given that the only opam package broken by this has already been patched, I see no reason to delay the change. |
|
We can merge this as soon as it is ready. It was ready yesterday, but now we have to wait for Jacques' review :-) |
garrigue
left a comment
There was a problem hiding this comment.
After a careful reading, it seems that the code is doing what you intend it to do (assume I understand correctly your intentions).
The only problems I found are an apparently wrong assumption (reflected in a variable name), but I don't think it has consequences, and a discrepancy with what I thought were your intentions originally w.r.t. -nolabels.
typing/typecore.ml
Outdated
| if optional && List.mem_assoc Nolabel sargs then | ||
| eliminate_optional_arg () | ||
| else begin | ||
| (* No argument was given for this non-optional parameter, we |
There was a problem hiding this comment.
What lets you think that this parameter in non-optional ?
The if-condition is a conjunction, i.e. we are here either because l is non-optional, or because it is optional but there is no unlabelled argument left in the application.
So the variable name is probably wrong (should be omitted_parameters).
There was a problem hiding this comment.
Good point, thanks!
I pushed the change you suggested (and updated the comment).
typing/typecore.ml
Outdated
| (remaining_sargs, use_arg sarg l') | ||
| else if optional && | ||
| not (List.exists (fun (l, _) -> name = label_name l) | ||
| remaining_sargs) |
There was a problem hiding this comment.
This looks like another departure from the current semantics of -nolabels.
Currently:
# let f ?x ?y () = ();;
val f : ?x:'a -> ?y:'b -> unit -> unit = <fun>
# f ~y:3;;
- : ?x:'a -> unit -> unit = <fun>I.e., you need a non-labeled argument to erase optional arguments.
But with this code, we would have:
# f ~y:3;;
- : unit -> unitSo we really need a new specification.
There was a problem hiding this comment.
I should have made this more explicit, sorry. But this doesn't feel like "another departure" to me, I see it as a result from the fact that optional arguments cannot be commuted anymore.
|
OK, just to clarify. |
Reading the message, and re-reading the code, I realize that the semantics is even more involved. |
|
My understanding was that you (@garrigue) and @trefis had discussed this offline and that you both agreed that the new semantics is a good idea. Given that no one cares about |
|
There may have been a misunderstanding. I have indeed said that I am open to a change in semantics, as -nolabels is very rarely used in a meaningful way. But I was not expecting a short time decision, and I had not looked at the details of the code yet. The code is fine, but it showed me that my understanding of this semantics was only partial. |
|
Or I am just too nervous, and it can be proven easily that the new behaviour is only a restriction of the current one? |
|
Well, no. It would still require that the -nolabels code check for the presence of an unlabelled argument, which it doesn't. Sorry for thinking aloud. |
ab39d5d to
d981fc7
Compare
|
Let's leave @garrigue, who is the author and caretaker of this code, to make further decisions on this PR. I suspect that my comments have only increased the noise in decision-making, and I'll unsubscribe now. |
d981fc7 to
4bb63de
Compare
|
@gasche: I agree that @garrigue should probably have the last word on this topic, but I think it's good (and helpful) that people (Alain and you) spontaneously voiced an opinion on this topic. @garrigue and I chatted again, and we reached the following conclusion:
|
garrigue
left a comment
There was a problem hiding this comment.
I am now convinced that this is now just a restriction of the previous behaviour.
The author of the single incompatible code available on opam is informed, and the fix is trivial.
LGTM.
| Error: The function applied to this argument has type | ||
| ?x:'a -> a:'b -> ?y:'c -> z:'d -> unit | ||
| This argument cannot be applied with label ?y | ||
| Since OCaml 4.11, optional arguments do not commute when -nolabels is given |
There was a problem hiding this comment.
This error message makes everything clear.
| ?x:'a -> ?y:'b -> unit -> unit | ||
| This argument cannot be applied with label ~y | ||
| Since OCaml 4.11, optional arguments do not commute when -nolabels is given | ||
| |}] |
There was a problem hiding this comment.
And this incoherence is fixed. We have now a pure restriction.
This was useful when commuting optional arguments was allowed with `ignore_labels = true`. Which it isn't anymore.
f7edce1 to
a321395
Compare
Foreword: IMHO this is a fairly low-key "cleanup" PR. I hope to send some more PRs of that sort (on that particular bit of code) in the coming days, so I would ask potential reviewers to not spend to long suggesting further changes / clean-ups / simplification to this part of the code.
Of course that's only a suggestion, and if people disagree then I can come back in a few days with a bigger (and likely harder to review) PR :)
If I understood correctly, the split between
sargsandmore_sargs(which I spent a while trying to find meaningful names for, to no avail) exists only to be able to raise an error when an argument gets commuted even though-nolabelswas passed.I believe this can only happen for optional parameters, as in this example:
It is still unclear to me why optional parameters are handled in that way in this mode. Note: I understand that unlike named parameters, they cannot be applied without a label. But I believe (perhaps wrongly) that the implementation could be more obvious in that regard.
Anyway, as I said, that's a discussion for another day!