Open davethegr8 opened 3 years ago
This would only be a problem when used in a statement position - but the value of do expressions is to use them in expression position, where a do..while loop is invalid. In your above example, without the while
it'd be an error, no?
hmm...
do { 1; } while (1) { console.log(1); }
is valid code, so do blocks would break web compatability, or at least be weird to look at.
...without the
while
it'd be an error, no?
Currently, it would be, but expressions are valid where statements are, but not vice versa.
Valid statement:
1;
Therefore, "do blocks" should be valid anywhere:
do { 1; }
Maybe there's a better name for these?
"do expressions" are designed to be similar to "block expressions": you cannot use them to begin a statement.
// invalid
{ foo() {} };
// valid
({ foo() {} });
// invalid
do { 1 };
// valid
(do { 1 });
Oh, my apologies for not noticing that. Then as was already said, this problem wouldn't occur without attempting to put a do while in a rhs position.
I do think the syntax is poorly named. I understand the usefulness of creating a temporary scope, but perhaps let x = run {'val'}
would be less ambiguous?
The reason for using do
is that it's already a keyword which makes parsing the new syntax much easier.
I don't think do is a good name to use. Actually it increase the burden. You can not use it as the start of ExpressionStatement cause it will conflict with do statement.
If we change to another keyword we can avoid this problem, no need to add ( ) outside.
Why would want to use do at the start of an expressionstatement? Just use a block instead.
@pitaj Parsing the new syntax for current JS programmers? Future JS programmers? Parsers? It has a very different use from do while
, so I think it's likely do
is more ambiguous for all three groups.
The concrete advantage to using an existing keyword is that a “no line terminator here” restriction wouldn’t be necessary (to avoid ASI issues).
@ljharb I was under the impression run
and a bunch of other standard words were already marked as keywords, but had no use in JS. But I just looked at that list, and run
isn't on it. What about eval { 'val' }
? Since eval()
is provided by the language, it would be easy to overload. It's a less ambiguous overload for the parser, and it also conveys to coders much more how the new syntax would work since it has the same behavior as eval()
.
there’s no way anyone would want to make more uses of the word that shall not be named.
@mikestopcontinues eval
is also not a keyword. let eval = () => 0; eval('1'); /* 0 */
works fine.
OK, I'll stand by run
or some other new keyword. If the functionality isn't worth the trouble of a new word, is it really worth being added to the language?
Adding a new keyword causes backwards compatibility issues, new keywords are very unlikely to be accepted.
Especially something as common as run
What about how get
and set
are contextual keywords? eval
could be the same: if it's followed by a '{', then it could create a block expression.
@00ff0000red that's already valid syntax, due to ASI:
var anyNonKeyword = 3;
var foo = anyNonKeyword
{ /* this is a block */ }
that would break if anyNonKeyword {
suddenly became "the result of the block", unless there was an NLTH restriction.
That still causes ambiguous grammar when considered with other language features and automatic semicolon insertion.
const f = () => eval
{
12;
}
Currently this is an arrow function declaration followed by a block. But it becomes ambiguous under your proposal. If you replace eval
with do
it is unambiguous.
Personally I don't think ASI is very important in this specific case, even we use do
, the mainstream coding style will be always do {
(no newline between do
and {
), so there is no much difference in real-world practice.
Actually, the inconvenience of do expression can't be the start of ExpressionStatement may be worse:
foobar
do { ... } |> f // syntax error!
So you are forced to write
foobar // <- ASI!
(do { ... }) |> f
and introduce a real ASI hazard.
Just spitballing.
"use expr"; // modifies current context so that if statements becomes if expressions
const conditional = if (age<21) { false } else { true }
// the if expression can be spec'ed elsewhere however you want
I feel like this is the most non-breaking way to introduce new expression syntaxes without overloading keywords or introducing new ones.
Yeah, actually I think we don't have to stick on the do
keyword. Why not a new one just like in pattern matching.
`match` <No LineTerminator here> Block
For example, we can use
`expr` <No LineTerminator here> Block
And some of my friends usually think "do expression" is "do notation" (Haskell) in JS
For example, we can use
`expr` <No LineTerminator here> Block
Yup, that's a possibility I'm considering. (If I go with that, I'll might also change async do {
to just async {
.)
That also removes the limit of do expression cannot appear as an ExpressionStatement strat
Technically true, though there is no reason to do that ever.
Yeah I like the idea of expr {...}
, and maybe we could just use eval {...}
, so people could simply understand it as sugar for eval("{ ... }")
(though we make the eval {...}
more stricter to avoid some confusion).
It's very much not sugar for that, so eval
would be a very misleading name. eval
's connotation is "string input", which is not the case here.
Whether it's a sugar or not, is depend on the final semantic we choose. If the semantic is closed enough,let's say, if almost all do {CODE}
could be replaced by eval("{CODE}")
and return the same result except those throw syntax errors, it will be treated as sugar anyway by most people in the community.
eval's connotation is "string input"
I think it's just like the case of import x from mod
and import(mod)
.
Note I only think eval {}
is a possible solution if the semantic is closed enough. If not, it of coz should not use eval
.
...we could just use eval {...}, so people could simply understand it as sugar for eval("{ ... }")
Sugar for eval... that legitimately sounds awful, and I would lose all faith in ECMAScript and TC39, should the members of TC39 go forward with that idea.
But let's not have a full blown debate about eval and it's semantics, those are not what are being proposed here.
I very much don't agree that it would be treated as sugar. eval
is bad, using eval
is bad, and "turning a list of statements into an expression" is not bad and won't be lumped together with eval by anyone in the community.
Even if the semantics are identical, it would be hugely harmful for tc39 to in any way equate, or imply equation with, do
expressions to eval
.
The eval
function is bad because it is converting an arbitrary string into code.
But if we take the literal meaning of eval
, "evaluate a piece of code", that is totally not bad.
BTW I still like the idea of expr <No LineTerminator here> Block
.
@Jack-Works yes, the program evaluating code is indeed bad - only the engine should be doing that. do expressions are just a syntactic marker around a statement list that produced an expression value; equating this to eval doesn’t make any sense to me.
What about going for if-expressions instead? It seems like "ternary hell" is a large part of the reason why do-expressions are requested.
If-expressions are less powerful, but also less verbose. Implementating them should be relatively easy as they would be syntactic sugar fo the much hated ternary expressions.
And in either case, expression-like if-statements are needed to implement do-expressions. if-expressions could be a stepping stone for do-expressions.
const html = `
<main>
${if (user) `
<strong>Hello ${user.name}</strong>
` else `
<strong>You need to sign in to use this service!</strong>
`}
</main>
`
The syntax error could be a bit confusing though.
const html = `
<main>
${if (user) {
^ Maybe this should throw "SyntaxError: Object literals must be wrapped in parentheses in if-expressions"
`
<strong>Hello ${user.name}</strong>
`
^ Or here with "SyntaxError: Unexpected template string" as this would be an object literal and not a block
}}
</main>
`
It wouldn't support highly scoped temporary variables. But IIFEs are not that complex and I do not believe this will be the primary use case for this proposal in real life. Can anybody who have used do-expressions extensively comment on this?
const unsubscribe = (() => {
let value = null
return onChange(nextValue => {
if (value !== nextValue) {
value = nextValue
doStuff()
}
})
})()
vs
const unsubscribe = do {
let value = null
onChange(nextValue => {
if (value !== nextValue) {
value = nextValue
doStuff()
}
})
}
Are any of these options viable?
in do { ... }
do => { ... }
// I'm assuming these are NOT viable
// only because they steal useful future
// syntax opportunities from identifiers
as { ... }
from { ... }
As far as I know, all of these are currently SyntaxErrors.
But, the last two specifically I imagine have controversial implications; just thought I'd throw them out there because I like how they read.
The arrow one also implies it’s a function body, which it’s not.
If the return result of do
must be assigned to a variable. Can we redesign the syntax from the assignment statement, for example, using the following syntax.
let x := {
1
}
// or `= get` as combine keyword
let x = get {
1
}
If we want to only performs some side effects or hide some variables in local block, then we can replace the do
with ignore
.
ignore {
let x = 1;
console.log(x);
}
// or hack
if("print local x"){
let x = 1;
console.log(x);
}
do expression another usage case:
let x = true?(do {
1
}):0
It doesn’t have to be assigned to a variable; that’s one of the whole points of it.
Using get {}
instead of do {}
is better both on semantic reading and on compiler design.
I think it's just like the case of
import x from mod
andimport(mod)
.
import
in import(mod)
is still a reserved word, it just happens to look like a function. eval
is just a function.
I'm worried that these two similar constructs are going to cause a lot of confusion in JS developer land. I foresee people being very confused with the differences, especially if a do block / do while loop is very log and they miss the end of it.
Also, I can see confusion / problems come up because of ASI, namely code like this:
If the loop was written poorly, I could see it becoming infinite. I could also see the other side, the above code being an intentionally a do block followed by a while loop.