Conversation
|
I agree this would be a useful feature. Some Lisps call the single-branch when condition do
expression
doneI think I'd prefer to have a single distinct keyword rather than a distinct set of keywords to distinguish the one-branch and two-branch cases. |
|
Two questions:
|
|
Well supporting both |
|
One less ambitious proposal would be to add warning 57, and just rewrite offending conditionals into (Also "imperative if..then..else" is a really ugly name. You could use "delimited" or "terminated" instead of "imperative". Really, would you hope to get a change accepted that adds more "imperative" stuff to the language ?) |
Could you elaborate, I don't quite follow what you mean? Also, whilst the single-branch
The typing is different:
As @gasche says an optional terminator causes grammar conflicts, consider: if ... then
foo ();
bar ()
endThis is not LALR since you do not know when you see the |
Why the restriction ? |
It keeps the |
|
But there are lot's of cases where you want a terminated |
|
We're paying the price for having tried to be too terse. What we really needed is ruby's approach, where you have if ... then ... [else ...] end. Relying on a semicolon to terminate the if expression was a bad idea. Same applies to our match expressions, where ocaml tried to get away without terminating, but we then have to wrap (some) nested match expressions with begin...end, so we end up paying more. |
|
I agree with drup on this one. It's consistent with the usual syntax but there is a lot of usecases in which it's useful to allow the new syntax to return something else than unit. |
Could you give an example? According to the manual |
|
I don't think the new syntax is a good idea. The example should already been enclosed between if condition then
begin match x with
| stuff -> blah (); Deferred.unit
end
>>= fun () ->
other_stuff ();
foo ()Relying on |
|
@gasche, what does warning 57 do? |
|
@lpw25 Also, it's just plain clearer in almost every cases. |
|
"Warning 57" is the best name I found, in the context of this discussion, for the new warning added by @trefis patch that warns whenever an Have a look at the pull request. Thomas not only introduced a new construction, he also implemented a warning to detect a (subset of) fragile use-cases for the old construction -- and converted them to the new one. I propose that (regardless of whether the new syntax is deemed useful or not, and frankly I expect the discussion to be extremely boring) we integrate the new warning as soon as possible. If the construction is added, it can be used to silence it, otherwise I personally suspect that |
On the contrary, it's pretty common monadic style. Are you honestly going to claim that this issue doesn't case bugs? There is plenty of evidence that it does.
Sure, but that is not the issue being addressed. Issues with constructs of higher precedence can always be safely solved by just adding a Basically the |
|
@gasche I agree. I think that's a good first step. |
|
@lpw25 I agree it causes bugs, but I don't think the solution is a good one. Now, purely hypothetically, what if we introduced a breaking syntax change (feature-gated by a flag similar to (ok, I know, not a chance…) |
|
@gasche said:
The actual implementation of the warning is dead simple: "trigger when an if expression is followed by a semicolon", we allow ourselves such a simple check because we know that a delimited form exists which could be used instead of the non-delimited one. As for using if <condition> do
<expr>;
<expr>
done;
<expr>as proposed by this PR, one would have to write begin if <condition> then begin
<expr>;
<expr>
end end;
<expr>which is marginally more verbose, though probably not unbearable. From an implementation point of view, I would expect the changes required to the parser to be significantly larger. As for the ambitiousness of this new construction: it doesn't introduce any new keyword, nor does it break any existing code. |
I get your point, although I don't think it would be very large. Also, the cases that would require an annotation in the grammar also correspond to cases where, with your implementation, the warning is raised where there is in fact no danger -- and the information added to the grammar would also help if one day we want to have more faithful -dsource printing. |
|
What about the new warning being triggered if the “then” (or “else”) clause is terminated by a semicolon or possesses a semicolon in its body, and it is not braced? if condition then
begin match x with
| stuff -> blah ()
end;
other_stuff ();
foo ()you would be required to write if condition then (
begin match x with
| stuff -> blah ()
end;
other_stuff ();
foo ()
);In the name of safety, I do not see why we wouldn't then “impose” (under the new warning) that the following be braced too (as displayed): if condition then
do_someting()
else (
begin match x with
| stuff -> blah ()
end;
other_stuff ();
foo ()
);Following the same trend, why not also apply the same warning to: if condition then
let x = 1 in
y;
z(badly indented on purpose!). if cond then raise X;used, in particular, as if cond then invalid_arg "...";(if it is turned on by default, the new warning will then affect quite a few codes — although I do not mind personally). |
|
I dont understand how the compiler will know that |
@ygrek It will not. It will notice that the if condition then (
begin match x with
| stuff -> blah ()
end;
);
other_stuff ();
foo () |
Sure. The stated goal is to have a form of The proposal is to introduce a second form of if condition do
expression
doneI have a slight preference for using when condition do
expression
doneOne reason I prefer
That's true, but the single-case |
|
I also wonder whether the problem could be solved with better tools. Warnings are one possible area to improve, as discussed above. Additionally, you could avoid the problem altogether by automatically indenting your code. if condition then
begin match x with
| stuff -> blah (); Deferred.unit
end
>>= fun () ->
other_stuff ();
foo ()and the second example like this: if condition then
begin match x with
| stuff -> blah ()
end;
other_stuff ();
foo ()which makes it pretty clear what's going on. With an editor configured to indent code automatically the problem should show up during refactoring. |
|
I feel pretty strongly that ocp-indent is not a sufficient answer to this issue. For one thing, lots of people (ourselves included) use whitespace insensitive diffs when reading code, which means that whitespace only changes that should be significant may be missed. But more than that, OCaml is not a whitespace-sensitive language, and one shouldn't need whitespace to have a decent shot at interpreting OCaml code. It's worth noting that these cases can show up in single line contexts as well. The semantic difference between these two cases: will not be made less confusing by fixing indentation. |
|
@yallop: do you also think when is the right choice for the "when else" case, e.g.: My intuition is that if is clearer in this case, but I'm not certain. |
|
@lpw25: I'm unsure about the must-return-unit rule. How about this kind of example: This seems like a natural case where you'd want your terminated if to actually return a value. |
|
@yminsky, but in this case regular |
|
@Chris00 in your example there is already
already holds |
|
I agree with Jacques that this problem would need a full solution. Yet, I am not happy with adding an alternative |
Yes. I wasn't clear but this is the condition I meant. |
|
Sorry, arriving too late but I do not see any big benefit to introduce a new syntax for this problem. |
|
On 2015/11/07 11:00, camlspotter wrote:
The problem occurs with else too, so this is not sufficient if cond then … else expr1; expr2 Here whether expr2 should be included in the else clause or not is ambiguous. The other problem is verbosity: if cond then begin …; … end else () is longer (and arguably less clear) than if cond do …; … done By the way, I’m not convinced yet that if-do-done solves all problems. let wait () = print_string “Press enter\n”; read_line () let g () = This code returns a string, so it cannot be replaced by do done, but |
|
@garrigue I believe in the case you describe, one would have to write: I agree that there's something odd about the fact that the "do" notation doesn't work in this case, but there is an escape hatch. |
|
@garrigue said:
That is true, however in this case warning 10 will trigger (or an error if you're using -strict-sequence). This doesn't point exactly to the error we are considering in this PR but still indicates that the code is dubious which is what we want to hear. |
|
@yminsky: The important conclusion is still that, if we add if-do-done, we can just warn on any if-then or if-then-else followed by a semicolon, and this without restricting the language. |
|
@garrigue: As to the question of the inelegance of adding a second if statement, It's worth noting that lots of languages have a separation between an imperative if statement and an expression-oriented one. We're really just mimicking that structure here. It's also worth asking the question of, if you were starting from scratch, what better solution would one pick? Would you require an endif? That puts an extra syntactic tax on expression oriented code, which the imperative-if proposal doesn't. It might be that separating out the imperative case and requiring extra syntactic markers only then is actually the right solution, even not taking history into account. Another question is concision of the if/do syntax. On some level, it would be nice to dispense with the multiple one might prefer But then the question arises of how to handle the "else-if" pattern. It works fine in @lpw25's proposal: But it's not clear how to support this with a more lightweight syntax. You could do something like this, I suppose, but it then has the odd property that do's and dones aren't matched up. I have mixed feelings about which choice is preferable, but the above does seem harder to think about, and so is probably a bad idea. |
|
What's the problem with the following? It just reflects the structure of the code. And it's very hard to forget one. |
|
Please excuse my interjection, despite the fact that I have contributed zero value to the OCaml community otherwise. I hope you will give my thoughts serious consideration anyway.
Finally, I feel that the core of many of OCaml's syntax pitfalls are related to the precedence of the unguarded semicolon. In this case, the poor In this example, the semicolon binds tighter than we'd like it to (as opposed to more loosely as in the case of the let myAB = {
a=fun i -> print_string "calling a";
b=b;
}The above example is parsed as: let myAB = {
a=fun i -> (print_string "calling a";
b=b);
}While I wish semicolon didn't have this issue in the first place, it would be unfortunate to introduce a new AST node type (beyond a ghost expression) for the notion of "record field value that doesn't conflict with semicolon": let myAB = {
a=do_field fun i -> print_string "calling a" done_field;
b=b;
}Especially if it required changes to let myAB = {
a=(fun i -> print_string "calling a");
b=b;
}Thank you all for listening. |
|
I think there is an ambiguity in the refactoring example: do we want to warn on the original code, on the wrongly-refactored code, or on both? I've been assuming the second, but I'm under the impression that some people here are assuming both.
That's right, assuming we only want to warn on the wrong refactoring.
I'm not so sure. And even if we add a new syntax, we'll need this warning. So we should add the warning, then see what happens and revisit the question of adding a new syntax in one year or so. About the implementation, I agree with @jordwalke : since this new proposal is a purely syntactic warning, it should be implemented in the parser. |
|
It seems clear that there is no consensus on the proposed solution and little chance of getting one. It is nicely executed work, and it does highlight a deficiency of the OCaml syntax, but there is no chance of satisfying users with the proposal in its current form. I'm closing the pull request. |
|
I disagree. I planed to work on adding the warning (or some variation of Le dim. 22 nov. 2015 17:40, Gabriel Scherer notifications@github.com a
|
|
I am coming late. My two cents:
Francois. |
|
Hi everybody ! My two cents (this PR will become rich). Going back to the origin of the problem, where indentation is erased on purpose: In most programming idioms (C,Java,...), this strictly reads as The troubling point in OCaml is that, now: is parsed as If a new warning is created, I would expect to be warned on the second form, not the first one. Of course, depending on the desired coding style, both warnings might be meaningful. |
|
So it looks like it took a company to solve the problem that we weren't willing to do ourselves. I guess at this point we could say that a syntax is just a modular front-end to OCaml. So maybe we can take a hint from Reason and fix the things that were broken in the way that functional language enthusiasts would prefer? If people are going to switch syntax anyway, why not give them a choice of a more modern OCaml syntax to select instead? |
|
This PR has been a fantastic read! It inspired me to propose a related feature: #1122. |
…oots Revert "Let only one domain scan the globals"
A classic source of bugs in ocaml programs is related to the precedence of if statements relative the the precedence of semicolons.
By that I mean that:
is parsed as
(if E then E); Eand not asif E then (E; E)in spite of what the indentation seem to imply.It is an error we too often encounter at Jane Street, consider for example the situation where one refactors
into
This kind of error happens often enough (among newcomers to the language, as well as veterans -- see mshinwell@843ea4d#diff-f51c83f68b1c1139bb40787fec5d5ec2L425 ) that we would like compiler support to avoid them.
Proposition
The problem arising when using the open-ended if construction in presence of semicolons, we propose introducing a "terminated" version of
if.Single branch if statements would look like this :
and the ones with several alternatives look like this
In addition to this construction a warning (off by default) is added which triggers when the "traditional" open-ended
if .. then .. else ..construction is present in an imperative context, by which we mean: on the left hand side of a semicolon or inside ado .. doneblock.The choice of
do .. doneas delimiters for this new if construction comes from the similarity of this form with the other constructions of the language traditionally associated with imperative programming (forandwhileloops).Indeed as is the case with loops, this construction insists that the content of the branches be of type unit.
Implementation summary
Pexp_ifdoconstruction is added to the parsetree.if .. do .. doneformI am not particularly attached to the current name and message of the new warning; any proposition to improve it is welcomed.