I will start with a different idea that fits the title of this post, and then I will show that it is more on-topic than that by going back to the discussion about concrete syntax.
Rust has “if lets” (reminiscent of C++'s if-declarations), which allow writing something like: (OCaml pseudo-code)
if let Some x = e then f else g
Above, the scope of x is f. In Rust this is useful because the pattern-matching syntax is a bit more verbose so this saves a greater amount of keypresses and eye strain.
I have been wanting to write if-lets for a while in OCaml, for a slightly different reason: match-with does not play nice with semicolons. Compare:
d ;
(match e with
| Some x -> f
| _ -> ()
) ;
g
or even:
d ;
let () = match e with
| Some x -> f
| _ -> ()
in
g
with:
d ;
if let Some x = e then f ;
g
(This appears to be already properly understood in emacs indentation-wise, so you can try by yourself.)
This would lift one oddity of let-bindings: although it has always been possible to write let A x = ...
, unless this is a single-variant type or you are a proof assistant generating OCaml code it is currently not possible to use it because of the exhaustiveness warning and the Match_failure exception.
(I imagine one natural objection, which might explain why if it has been thought about in the past it was dismissed, is that it can lead beginners to write code like:
let x = e in
if let A y = x then ...
else if let B y = x then ...
else if let C y = x then ...
where they lose exhaustiveness checking. So the language is designed to guide them towards match-with. I’ll reuse @yallop’s argument, that this form is for when the working programmer intentionally avoid exhaustiveness checking, and that one can even explore adapting his radical proposition to warn if it is exhaustive by accident. A convincing special case of warning is the accidental if let A x = e then ...
where A is the single variant.)
Unfortunately, and
in let-bindings does not nest scopes, e.g.:
# let x = 3 and y = x;;
Error: Unbound value x
It would be very confusing to introduce a similar syntax with very different scoping rules. (Other syntaxes using and
proposed above have the same issue as noticed by @recoules.)
One can solve this by forcing to repeat let
after and
. So one would be able to write:
if let A x = e and let B y = f(x) then g(x,y) else h
which would shorten the following:
match e with
| A x -> (match f(x) with B y -> g (x,y) | _ -> h)
| _ -> h
(note the duplicate h).
One benefit of having to write the full and let
is that one can finish with a conditional expression:
if let A x = e and let B y = f(x) and cond(x,y) then g(x,y) else h
To sum up, the syntax for ifs becomes:
‘if
’ cond ‘then
’ expr ‘else
’ expr
where:
cond ::= expr | ‘let
’ let-binding [’and
’ cond]
This mostly reuses existing syntactic forms, and I believe this does not introduce grammatical ambiguities.
What’s more, the idea can be extended to other places expecting a boolean expression: while
and when
. In other word, the same idea gives you the let-bindings nested inside match, one of the extended pattern-matching syntax discussed above. (As I promised at the beginning.)
For instance @trefis’s example becomes:
match f () with
| Some y when let A x = g y and let Ok result = h x -> result
| _ -> raise Not_found
In this particular case this can further be simplified (depending on the context) as:
if let Some y = f () and let A x = g y and let Ok result = h x
then result
else raise Not_found