-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Are braces required? #1
Comments
Thanks for registering this one. I'm not ready to come down on either side of this one just yet. :) |
It would be nice to be able to write const json = do try { JSON.parse(data) } catch (err) { ({status: 500}) }
const message = do if (json.status == 200) { "success" } else { "error" } |
The main point in favor of removing braces from do expressions are JSX interpolations, of course: function MagicButton(props) {
return (
<button>
{do if (props.useIcon) <MagicIcon />}
{props.children}
</button>
);
}
function MagicList(props) {
return (
<ul>
{do for (let incantation of props.incantations) {
(<li>{incantation.name}</li>);
}}
</ul>
);
} (I’m trying to simulate prettier output here) But more generally as prettier or similar is adopted and enforced in projects having “useless” braces makes the feature less appealing: function SomeComponent(props) {
const onClick = do if (props.disabled) { props.onClick; } else { null; };
return <MagicButton onClick={onClick} />;
} With braces and prettier formatting would result in function SomeComponent(props) {
const onClick = do {
if (props.disabled) {
props.onClick;
} else {
null;
}
};
return <MagicButton onClick={onClick} />;
} |
One main argument in favor ok keeping braces is clearance (IMHO) of semantics. Braces have major visual impact and when formatting is enforced has even more estate to help. But, still IMHO, this gives no actual help since an This line could not be confused with anything else: const type = do if (value === 42) { 'answer'; } else { 'not-answer' }; |
but this one could: if (oracle()) do if (value === 42) { return 'answer'; } else {return 'not-answer' }; |
what about: let foo = [do 3+4, 5+6]; is foo [7,11] or [11]? similarly, f(do 1,2) How many arguments are passed to f? |
I find these discussions fascinating. I would err on the side of consistency with previous constructs, assuming there's a new construct here. |
I'd err on how
I am not clear on a simple way to look at this, but I feel like it has a similar answer to how: do {
}
while (true); Needs to remain unambiguous. That if (true) if (false) {c();} else {d();} calling |
@yuchi, off topic, but
does not do what you want - you'll only end up getting the last (This type of misunderstand does make me a little worried about this feature - very few JS developers today are familiar with completion values, and rightly so.) |
@bmeck The fact that we can come up with an answer for these does not inspire me with confidence that we should come up with an answer for them. ^_^ Braceless-if is, except in a small number of cases, a huge mistake that has caused untold millions of dollars worth of damage thruout its history. More braceless-if-a-single-statement constructs need a pretty strong motivation, I think; stronger than just "it's possible to define a consistent grammar". In particular, if we really want |
But it actually doesn't. Since the whole point of do-expression is to allow JS statements and statement lists to be used as expression elements, bracketless do-expressions would presumably be defined using a grammar rule like this: DoExpression : do Statement where DoExpression itself is defined as a RHS of some Expression component production. Probably PrimaryExpression. But the definition of Statement, includes: Statement : ExpressionStatement ExpressionStatement : Expression ; (I've simplified these BNF definitions by leaving out little details that aren't relevant to this issue.) So, when parsing something like But there is actually a further complications. Notice that semicolon at the far right of the definition for ExpessionStatement. That says that an ExpressionStatement must end with a So, both of these statements should cause syntax errors. let foo = [do 3+4, 5+6];
f(do 1,2); You would have to write them as: let foo = [do 3+4;, 5+6];
f(do 1;,2); or, depending on what you actually intended let foo = [do 3+4, 5+6;];
f(do 1,2;); or you could just use let foo = [do {3+4}, 5+6];
f(do {1,2}); (for why, see the rules for ASI. It's easy to forget that ASI isn't just about end-of-line semicolons.) I suspect that even if we allowed bracketless do-expressions various communities would end up with style guidance that says some like: To avoid syntactic confusion and errors, always enclose the statement part of a do-expressions with |
@bakkot Totally right. I honestly misunderstood the actual scope of the proposal. As an old coffee-scripter I inferred (wrongly) that do-expression would bring full statements-as-expressions in the language. This is indeed instead a proposal to bring completion values instead (the REPL or eval results if I’m not wrong again). @allenwb Sorry if it sounds stupid, but could explicit grammars be made for statements that can have a |
@bakkot Completion values can be weird sometimes, like the fact that By having them as IIFE's would open possibility for generator based ones as well e.g. the example from before could become: function MagicList(props) {
return (
<ul>
{do* for (let incantation of props.incantations) {
(yield <li>{incantation.name}</li>);
}}
</ul>
);
} I think this should be split into a separate issue though so I'll make one. |
@yuchi In theory we can define any syntax we want for each unique context. But from a human factors perspective it would be a terrible language design to have logically equivalent "statements" whose syntax differed solely based upon the usage context. |
Assuming that braceless do-expression syntax is let x = y ? z : do throw new Error("foo"); is valid only because of ASI; otherwise you’d have to write: let x = y ? z : do throw new Error("foo");; Worst case is when the programmer will scratch their head wondering why let x = y ? z : do throw new Error("foo");
['foo','bar'].forEach(f); doesn’t work as expected. |
This is related to #9 and #11, if common statements like I think that's the preferable case. Relieves the need for extra keywords but still allows multi-step |
I don't think that's preferable; I prefer the explicit indication that different rules are applying. Separately, currently: if (true) { ({ a: 2 }) }
['a'] // yields `['a']` However, if eval('if (true) { ({ a: 2 }) }')
['a'] // yields `2` Thus, it would, in fact, be breaking, thanks to ASI. |
Yeah, I expect that the biggest difficulty would be dealing with ASI. However, it should be possible to forbid the "expression |
If it is absolutely breaking, that is, there's no way around it like what @Kovensky suggested, then why are those issues being left open? |
@Jessidhia I'm both not sure it'll be possible; and also I don't think it's worth it. |
No, it is not the fault of ASI. It is because a block does not have a semicolon terminating it. Today, the following is valid and evaluates to if (true) { ({ a: 2 }) } ['a']; |
Seems like that could be fixed by making it so statements as expressions are only evaluated as such where there would currently be a syntax error: in the right hand side of arrow functions, variable initiation, and inside a pair of parenthesis, etc |
One random note because of a Twitter thread: If braces are optional, would it imply that there's no block scope for the do expression if it skips the braces? In other words, would the following work: do let x = 42;
x === 42; // true |
I think it would indeed be very confusing if there was a block scope where there were no block boundaries (curly braces). |
How about defining an alternative form that accepts common use case like |
I think there are really only two use cases for do expressions without braces: if and try. Maybe "throw" too if this proposal to make "throw" become an expression doesn't go through. Switch would be the other useful one, but it'll just be superseded by Match, when pattern-matching comes out. This seems like a great idea, and there's similar discussion for it going on here. |
There seems to be a lot of talk about how if we had a "do " semantics, then we would also be required to place an extra semicolon at the end of the chosen statement, e.g. like this:
Does it really have to be this way though? I don't know much about the Javascript grammar, but this feels more like an artifact of how the grammar is currently defined, more than a hard requirement. Shouldn't it be possible to say that when you're using the shorthand form do expression, and you're placing a single statement afterward, it should not expect a terminating semicolon to end the statement? (and in fact, I would argue that such a semicolon should even be made illegal, the Can anyone think of any potential parsing ambiguities that could come if we forbid the semicolon after the do-shorthand statement? |
The syntax can always be made unambiguous, but not how humans read code. As mentioned earlier in this thread, the specific example you mentioned would be parsed differently if you remove |
I don't see why it would or should. The trick seems to be to make the "do" not so greedy so it doesn't try and consume as much as possible. It will need some sort of precedence, even when a statement is used in its shorthand form. If we can give it less precedence so as to not have it consume comma operators or comma delimiters, then everything should check out, and work in an intuitive way. do x + 2, y + 3
// same as
(do x + 2), y + 3
// just like how `() => x + 2, y + 3` == `(() => x + 2), y + 3`
[do x + 2, y + 3]
// same as
[(do x + 2), y + 3]
// just like how `[() => x + 2, y + 3]` == `[(() => x + 2), y + 3]` |
Some more thoughts on how the do shorthand could work:
Next, let's make each statement have its own precedence. This precedence does not do anything unless the statement is placed in a do expression. When this happens, the statement will "consume" as much as its precedence allows. For example, in In general, I would recommend that we give all of the statements the same precedence as do if (false) 2 else 3 + 3, 3
// same as
(do if (false) 2 else 3 + 3), 3
do return 2 + 2, 3
// same as
(do return 2 + 2), 3
[do throw x + 2, y + 3]
// same as
[(do throw x + 2), y + 3]
// I vote we just disallow declarations, the same way they're not allowed when
// you do `if (true) let x = 2`. But if we allow them, we should probably make any
// following commas be interpreted as further declarations.
do let x = 2, y = 3
// same as
let x = 2, y = 3
// (i.e. that comma is not a comma operator) |
@theScottyJam That would introduce distinct syntax and precedence rules for For example: if (false) throw 1, 2; else return 3, 4;
// equivalent to:
if (false) { throw (1, 2); } else { return (3, 4); } Under your proposal, when you prepend it with do if (false) throw (1, 2) else return (3, 4) |
I think the ambiguity can be solved by restricting the Allowed
Not allowed
|
Good call @claudepache I guess another option would be to give all do + statements have a precedence lower than the comma operator, like statements already do in a way. Another option would be to make a comma operator without parentheses be a syntax error, as is being explored in the throw expressions proposal here. |
I see some discussion here of async function initialize() {
await Promise.all([
async do let foo = (await request('foo.json')).data, // Ignoring the comma precedence issue for the sec
async do let bar = (await request('bar.json')).data,
async do let baz = (await request('baz.json')).data,
]);
render(foo, bar, baz);
} ...which feels a little cleaner to me than doing the same thing today with IIAFE's and early declaration: async function initialize() {
let foo, bar, baz;
await Promise.all([
(async () => { foo = (await request('foo.json')).data })(),
(async () => { bar = (await request('bar.json')).data })(),
(async () => { baz = (await request('baz.json')).data })(),
]);
render(foo, bar, baz);
} I'll admit that the 1 line of declarations saved isn't a huge deal, and |
We had some discussion in IRC and there are arguments for both sides. E.g. it is nice to be able to just do
(this largely obviates throw expressions in my opinion)
But others pointed out that cases involving e.g.
if
are pretty confusing.I'm curious to see where the champion comes down on this :)
The text was updated successfully, but these errors were encountered: