Do-Expressions Proposal Stalled?

# Peter Jaszkowiak (9 months ago)

tc39/proposal-do-expressions

It appears that the do-expressions proposal, which has a high amount of interest, is currently inactive. There haven't been any contributions from @dherman since September.

This concerns me because the proposal is a hugely simplifying feature, which would make the language much more concise.

Currently there are multiple instances of duplicate issues and the lack of input means the proposal is going nowhere.

What's going on?

# Jordan Harband (9 months ago)

I wouldn't assume "stalled" merely because 5 months have passed; even TC39 members have lives outside of JavaScript.

Language proposals (rightly) often take years to make progress.

This proposal was on the agenda for the September meeting, but we ran out of time to discuss it. It was not added by the champion to the November or January agendas. Perhaps in March or May, there will be an update - we'll all have to wait and see.

# Naveen Chawla (9 months ago)

I'm not necessarily in favour of the proposal. I think the comma operator feature in current javascript already covers at least some of the use cases.

# Naveen Chawla (9 months ago)

Also, I don't find it very readable/clear when reading it in code. Maybe I'm missing the whole point, but the comma operator forces you to wrap each non-expression language construct (e.g. for loops) into a function, which makes the expression itself clearer (in my opinion) than a do-expression.

Also, it's very easy to accidentally add code after the last statement, breaking the code and hence causing bugs! This is not as much the case with the comma operator, since you have to add a comma, which is not "normal" in ordinary function code vs a semicolon)

Altogether, I'd be happier if it wasn't introduced.

# Naveen Chawla (9 months ago)

Oh, and also, I don't like the idea of allowing if-elses to be turned into expressions. That's what ternaries are for. Again, this harms readability and is another surface area for the "accidental code after last executed statement" bug I mentioned in my last post.

# Isiah Meadows (9 months ago)

First, that's something you could enforce in a linter. Second, just as a heads up, if/else as an expression is actually pretty common - consider Ruby as one example of this in a very procedural language.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# T.J. Crowder (9 months ago)

On Thu, Jan 18, 2018 at 8:23 AM, Naveen Chawla <naveen.chwl at gmail.com>

wrote:

Also, I don't find it very readable/clear when reading it in code. Maybe I'm missing the whole point, but the comma operator forces you to wrap each non-expression language construct (e.g. for loops) into a function, which makes the expression itself clearer (in my opinion) than a do-expression.

I believe the point is to allow a series of statements, not just an expression as with the comma operator; and (when a block is used with them) to provide a local scope for identifiers (which again the comma operator doens't provide). Basically an arrow function without the call overhead and (tiny bit of) syntactic cruft. E.g.:

With the do operator:

let x = do {
  let tmp = f();
  tmp * tmp + 1
};

without the do operator:

let x = (() => {
  let tmp = f();
  return tmp * tmp + 1;
})();

or in that particular case:

let x;
{
  let tmp = f();
  x = tmp * tmp + 1;
}

...but that wouldn't work if x were a const instead.

How would you address that with just the comma operator? You need a scope for tmp unless you want it bleeding into the outer scope.

I also have to say I find the comma operator very easy to misuse, leading to hard-to-read code. do would be clearer in those cases.

Also, it's very easy to accidentally add code after the last statement, breaking the code and hence causing bugs!

Can you give an example of what you mean by that? Randomly adding code in the wrong place is going to cause bugs, yes. :-) I mean, just generally...

I'm on the fence about do expressions. Yes, they provide a handy micro environment for a short series of statements. But given standalone blocks and arrow functions (particularly inline ones that are easily optimized), is it really worth overloading do with another meaning and adding to the human and computer parsing overhead? And encouraging inline logic rather than breaking into smaller pieces? I'm a firm "maybe" for now. :-)

-- T.J. Crowder

# Naveen Chawla (9 months ago)
const x = (tmp=>tmp*tmp+1)(f());

I've not seen a use case for do-expressions that can't already be done more elegantly. The proposal page certainly doesn't provide it, except for an opinion about how nested ternaries are "awkward". I've never found nested ternaries awkward, especially with good indentation:

question1 ?
    answer1 :
    question2 ?
        answer2 :
        answer3
# T.J. Crowder (9 months ago)

On Thu, Jan 18, 2018 at 9:51 AM, Naveen Chawla <naveen.chwl at gmail.com>

wrote:

const x = (tmp=>tmp*tmp+1)(f());

Sure, that's another way an arrow function (with its attendant overhead) could be used for that specific example. Harder to read than the do or the more verbose arrow I posted, but sure.

-- T.J. Crowder

# Naveen Chawla (9 months ago)

I find it easier to read. Less to read, same (or greater) level of clarity.

As for your question about the bug surface area:

const
    formattedInput =
         do {
              if(isValid(input)){
                   getFormattedValidInput(input);
                   postToServerAsync(input); //oops!
              }
              else{
                   getFormattedError(input);
              }
          }
;

You might call this "randomly adding code in the wrong place", but can you imagine a heavily nested conditional algorithm where it's not immediately clear you're inside a "do" expression?

I think the point of new features should to be to reduce the chances of bugs overall. I'd be curious if there was a single case of this with do-expressions, let alone overall.

# T.J. Crowder (9 months ago)

On Thu, Jan 18, 2018 at 11:39 AM, Naveen Chawla <naveen.chwl at gmail.com>

wrote:

...but can you imagine a heavily nested conditional algorithm where it's not immediately clear you're inside a "do" expression?

I can. I would call it a misuse of the do operator, just like it is when you do that with conditionals or nested callbacks or switches or arrow functions or (etc.), or some combination of same. :-)

-- T.J. Crowder

# Naveen Chawla (9 months ago)

I agree it's a misuse, my point is simply why introduce the possibility? Key question for me (for any feature): show how it can reduce the chances of bugs.

Arrow functions succeed, for example: by removing nested "this" contexts, thereby simplifying moving of code around etc.

Async await succeeds, for example: by linearizing and hence considerably simplifying the building and reading of asynchronous data flows in code.

Classes succeed, for example: by removing the fiddly overhead in establishing (especially multi-level) inheritance using prototypes.

The examples on the proposal page don't succeed, for me, in establishing how (if at all) using a do-expression could (vs the most elegant alternative currently), if not the converse.

# Michael Rosefield (9 months ago)

Just to stick my uninformed oar into this, I notice what do-while statements already return a value in the exact way as the proposal.

do {
if(true) 1;
else 2;
 } while(false)
// 1
do {
if(!true) 1;
else 2;
 } while(false)
// 2

Of course, since this is a statement rather than an expression, there's little you can do with that.

So I have 2 questions:

  1. Would changing do-while from a statement to an expression cause any issues?
  2. Would having the while clause optional (and leaving it out being equivalent to while(false)) cause any issues?
# Michael Rosefield (9 months ago)

Addendum: all non-function/class statement blocks seem to act like this,

for (x of ['val']) { x; }
// "val"

for (x in ['val']) { x; }
// "0"

let x = 1
// undefined
while (x) {
    x-= 1;
    x;
}
// 0

switch(1) {
    case 0: 2;
    case 1: 6;
}
// 6

This raises the general question: why not let all statement blocks be used as expressions?

# T.J. Crowder (9 months ago)

On Thu, Jan 18, 2018 at 12:21 PM, Michael Rosefield <rosyatrandom at gmail.com>

wrote:

Just to stick my uninformed oar into this, I notice what do-while statements already return a value in the exact way as the proposal.

FYI, it's not just do-while: Any statement (including the block statement) "returns" the value of the last expression evaluated within it. But as you indicated, that's only available to the JavaScript engine itself (and REPLs that integrate with it).

The purpose of do is to expose that to code. I'm sure Dave Herman looked at the ramifications of allowing statements as expressions in the general case and found issues (I'd be shocked if there weren't really quite major issues with doing that). Separately, without the leading do there's no way, syntactically, to use a block statement as the do expression (since in an expression context, the { meant to introduce the block parses as the beginning of an object initializer).

-- T.J. Crowder

# Michael Rosefield (9 months ago)

Ha, yes, seems we're on the same page here. I agree that do is necessary to distinguish between object literals (really makes you wish that standard keyboards had access to more symbols so that we could avoid all this overloading of meaning), but I'd love to know what the issues with using these blocks as expressions are. Perhaps we could use do in front of any statement to make it treatable as an expression; we could have pure do, do while (as distinguished from do-while...), do switch, do for....

# T.J. Crowder (9 months ago)

On Thu, Jan 18, 2018 at 12:39 PM, Michael Rosefield <rosyatrandom at gmail.com>

wrote:

Perhaps we could use do in front of any statement to make it treatable as an expression; we could have pure do, do while (as distinguished from do-while...), do switch, do for....

I believe the proposal does that (or will do if it's fleshed out) and that do { ... } is just a ramification of that when you use a block with it rather than another statement. At least, that's my read of the code shown in the Further Considerations and unresolved parsing question.

There's just too much left unsaid so far (in the proposal itself) to be sure, though.

-- T.J. Crowder

# Peter Jaszkowiak (9 months ago)

In the issues for the proposal people are discussing making most statements capable of being used as expressions. Then the value of do is less important, but still necessary for some things:

const thing = if (cond) { a; } else { b; };

Then you only need do to introduce a new block scope.