Btw are you using ocamlformat? With the default profile it makes all (?) those cases explicit by adding parenthesis. So it’s easier to get what is “wrong”
This is life changing. I have no idea how I spent all this time without knowing about ocamlformat on save. For some reason I just assumed that the vim “autoindent” was the best there was.
If, in the future, you suspect I’m doing something stupid, please call me out on it. Happy to learn more.
I would say the only confusion in functional code is nested match. But once you start using ; there’s lots of potential errors (as in: shift-reduce conflicts where it’s not self-evident which way it goes). For instance, you need to wrap the pattern match in begin ... end or let () = ... in to make the following correct:
match x with
| Some x -> print_int x
| None -> print_string "No int!";
print_newline () (* this line is inside the None branch! *)
Similar issue with if clauses (solution: wrap the scope with begin ... end):
if b then
print_string "(yes)";
print_newline () (* this line executed unconditionally *)
Which is why the following does not compile:
if b then
print_string "(yes)";
print_newline ()
else ()
Then the weird thing is that adding a binding changes the behavior, so that this is wrong:
if b then
let s = "(yes)" in
print_string s;
print_newline () (* this line is inside the if *)
The solution is to wrap the whole if inside begin ... end, or let () = ... in, which means that to guard against both previous risks you need begin ... end both around the if expression and inside each branch.
Then of course we have the dangling else problem (tbh I’m not sure that one is a “confusion”, I find the behavior rather natural):
if b1 then
if b2 then
print_endline "b1 and b2"
else
print_endline "neither b1 nor b2" (* this line acutally executes if b1 and not b2 *)
The conclusion is correct, but I find the reasoning is misleading. Better way of looking is, let ... in and match ... with bind as far to the right as possible, while if ... then binds tighter than ; but looser than any other operator: OCaml library : Ocaml_operators.
Also, a trailing ; is optional but allowed when it doesn’t upset the logic of what you want to express. E.g. (print_char 'a'; print_char 'b';) is fine.
I’m not sure if this always been in the manual but in the current version the table of precedences actually has all this information. Binding constructs (let, match, fun etc.) are the loosest binders (which is equivalent to saying they eat up everything if you think in terms of a recursive descent parser), then semicolons, then if then else, then everything else.
I’m curious, has it ever been any discussion of allowing the use of ; (semicolon) instead of the in keyword in let ... in expressions? I’m sure people would have very strong feelings about this, but it seems possible in theory, right?
That was done in ReasonML, which introduced a C-like syntax for OCaml, complete with statement-terminator semicolons. It was pretty controversial when it originally came out, and honestly I think C-like syntax for ML semantics doesn’t really make sense. let...in... is a syntactic form that packages up a binding in an expression with a clear structure. In C-like syntax that syntactic structure is gone but we are expected to keep the semantics in mind.