Do-Expressions Proposal Stalled?
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.
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.
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.
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.
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
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
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
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
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.
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
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.
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:
- Would changing
do-while
from a statement to an expression cause any issues? - Would having the while clause optional (and leaving it out being
equivalent to
while(false)
) cause any issues?
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?
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
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
....
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 puredo
,do while
(as distinguished fromdo-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
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.
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?