Skip to content

Universal do..done#1122

Closed
murmour wants to merge 1 commit intoocaml:trunkfrom
murmour:do-done
Closed

Universal do..done#1122
murmour wants to merge 1 commit intoocaml:trunkfrom
murmour:do-done

Conversation

@murmour
Copy link
Copy Markdown
Contributor

@murmour murmour commented Mar 26, 2017

This patch extends the parser with a new kind of expression -- do..done -- which is syntactic sugar for ((..): unit).

The initiative is largely inspired by PR #278.

Rationale

OCaml's syntax includes a couple of curious constructs:

  | 'while' expr 'do' expr 'done'
  | 'for' value-name '=' expr ( 'to' || 'downto' ) expr 'do' expr 'done'

What makes these constructs curious is the common part of the syntax -- an expression enclosed between do and done. This expression is special -- it's computed for effects, and has always type unit. The constraint is enforced by the type-checker, protecting the user from possible coding mistakes, as expressions of any other type make no sense as the body of the loop. And so, when we see an expression inside do..done, we immediately know that this expression is, by intent of the author, computed for its side effects.

Now, there are more kinds of expressions in OCaml, beside loops and loop bodies, that could be computed for effects. The simplest example is function call, which can have any type, including unit. But what if we want to suggest to the reader the intention of calling the function merely for its effects, not its value? In other words, how can we say that expression has type unit sans resorting to the clumsy ((..): unit) pattern?

One option is to abuse the semicolon operator, the first (left-hand) argument of which is assumed by the compiler to have type unit (otherwise, a warning is issued). Unfortunately, the right-hand argument can have any type, so I commonly find myself churning out something abominable like this:

begin
  a;
  b;
  c;
  ()
end

Or even this (horror!):

let () = begin
  a;
  b;
  c
end in

But wouldn't the following be nicer and more intuitively suggestive?

do
  a;
  b;
  c
done

And this is what my patch adds -- a new kind of expression wrapper, similar to parentheses and begin..end, which also checks that the enclosed expression has type unit. As simple as that.

I think that this change is good, because, on one hand, it doesn't break backward compatibility (in comparison to proposals like #715); on the other hand, it doesn't really make the language more complex. On the contrary: it's akin to simplification, as the feature was already there, just bolted to while and for, in an ad hoc way. And now it is generic and universal, applicable in a wide range of situations.

For example, PR #278 explored the idea of adding a specialized variant of the if construct, the purpose of which was to help the user avoid common pitfalls of a single-branch if. With do..done, we don't need a separate construct:

if cond then do
  ...
done

Even if universal do..done is a bit more verbose than "imperative if", in this particular context, I still find it superior, because, in my book, it is simple and composable features that comprise good design in programming languages.

Implementation details

Currently, do..done is syntactic sugar that's resolved in the parser, as I think it'd be overkill to extend the AST (which would only add noise).

A minor LR conflict had to be solved with a %nonassoc constraint.

As a positive side effect of unifying do..done inside the parser, loop bodies can now be empty (which equals to ()) -- this fixes the old nuisance of being unable to comment out contents of do..done (e.g. for debugging purposes). Also, it is now possible to attach attributes solely to the do..done part of the loop (as it is possible with the new do..done expression).

An illustration of these changes (correct piece of code):

for i = 0 to 10 do [@attr]
   (* nothing, which equals () *)
done

Expectations

I know this is going to be controversial and opinionated, so I'm not spending efforts on writing a proper test or updating the manual. Not yet. The implementation is a working proof-of-concept that should facilitate discussion and criticism. Please question my reasoning behind this design.

@murmour
Copy link
Copy Markdown
Contributor Author

murmour commented Mar 26, 2017

@yminsky, you wrote in comments to #278:

But then the question arises of how to handle the "else-if" pattern. It works fine in @lpw25's proposal:

if <cond> do
   <body>
done else if <cond> do
   <body>
done else if <cond> do
   <body>
done else do
   <body>
done

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.

if <cond> do
   <body>
else if <cond> do
  <body>
else if <cond> do
  <body>
else do
  <body>
done

Easy-peasy, with this proposal:

do
  if <cond> then
     <body>
  else if <cond> then
    <body>
  else if <cond> then
    <body>
  else
    <body>
done

@mshinwell
Copy link
Copy Markdown
Contributor

Given that there haven't been any comments at all on this issue, for more than four months, I think a reasonable judgement is to deem that it does not carry sufficient support for merging at present. As such I am going to close it. I have made a couple of people who have thought about the if-then-else issue aware of the proposal in case they wish to come back to it later.

@mshinwell mshinwell closed this Aug 2, 2017
@yawaramin
Copy link
Copy Markdown
Contributor

I upvoted this proposal a while ago; I thought it was an obvious syntax simplification. Now I think it hasn't been 'marketed' enough to a broad section of OCamlers. I think it needs more publicity; to that end I suggest we post it with a short description to the usual OCaml fora--Reddit, Discourse, caml-list, maybe the ReasonML Discord.

If no one beats me to it, I will start up this 'publicity campaign' in a few hours when I get home.

@ShalokShalom
Copy link
Copy Markdown

Did you?

@murmour
Copy link
Copy Markdown
Contributor Author

murmour commented Sep 4, 2017

He did, wow!

Thanks a lot for this, @yawaramin. Very heart-warming. :-)

I'd be happy to get any feedback on this proposal, even the most negative one. Silent treatment hits hard. :-(

@bluddy
Copy link
Copy Markdown
Contributor

bluddy commented Sep 4, 2017

The problem with this proposal IMO is that to be worthwhile, it should simplify the syntax, and to do that, it needs to not be backwards compatible. There are 2 basic approaches to solve OCaml's leaky if problem:

  1. Make sequencing statements require do...done. If statements cannot stand on their own, they cannot leak.
  2. Add end to if constructs (as in my proposal, -safe-syntax option #715).

Adding do...done without making it the exclusive way to run statements doesn't improve much IMO. Personally, I like approach 1, but given the fact that we also want to fix leaky match expressions, I think 2 becomes the more consistent approach.

stedolan pushed a commit to stedolan/ocaml that referenced this pull request Mar 21, 2023
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants