Mark S. Miller (2015-07-13T23:33:51.000Z)
d at domenic.me (2015-07-25T02:52:09.901Z)
Interesting. Got me thinking. Here's an alternate proposal I'll call "do expressions without the 'do'." At https://people.mozilla.org/~jorendorff/es6-draft.html#sec-expression-statement we have the syntax of the expression statement. Ignoring sloppy "let" nonsense, this says that an expression statement cannot begin with "{", "function", or "class". At https://people.mozilla.org/~jorendorff/es6-draft.html#sec-ecmascript-language-statements-and-declarations are the legal ES6 statements. Note that most of these begin with a keyword that cannot possibly be legal at the beginning of an expression. Therefore, adding all these initial-statement-keywords to the list of things that cannot begin an expression statement would break nothing. They already cannot begin an expression statement. With the expression statement prohibition in place, now we can allow all these forms to be expressions. As with "{", "function", or "class", if you want to state such an expression in expression-statement position, surround it with parens. Because all these new forms will look bizarre and confusing, at least at first, let's say these always need surrounding parens to be expressions. I think that would help minimize confusion. If we do this, the oddest duck is "{", since it begins an object literal expression. This proposal gives us no straightforward way to express an block expression. "function" and "class" are less odd, since their existing expression forms mean what you almost might expect by this new rule -- even though they are initial-declaration-keywords rather than initial-statement-keywords. The remaining initial-declaration-keywords are "let" and "const". We already made "let" insane regarding these issues in sloppy mode, so I'm going to ignore that. But let's consider "const" and strict "let". These already cannot appear at the beginning of an expression, so it would not break anything to add them to the prohibition list for the beginning of expression statements. No current expression can add any binding to the scope in which the expression appears. Let's examine the consequences of having parens -- rather than containing a "{"-block to create a nested scope with a value (which would conflict with object literals), instead simply define a block-like nested scope with a value. This would allow declarations and statements within the parens, much like the current "do" proposal. It would even be consistent enough with the existing semantics of paren-surrounded function and class expressions: Someone who sees these as a function or class declaration within its own nested scope, whose value was the value being declared, would rarely be surprised by the subtle difference between that story and the current semantics. Having parens accept a list of declarations and statements rather than just an expressions seems like a radical change that must break something, but I can't find a problem. Am I missing something? Examples inline: On Mon, Jul 13, 2015 at 5:47 PM, Isiah Meadows <impinball at gmail.com> wrote: > I was reading a recent thread > <https://esdiscuss.org/topic/allow-try-catch-blocks-to-return-a-value> where > do-expressions simplified a common try-catch use case, and I was wondering > if `do` could be simplified to an expression? It would allow for this to be > solved very easily, but also add a lot more flexibility in this proposal, > as well as avoiding some ugly nested braces. > > I know it would cause an ambiguity with `do-while` loops, but that could > be resolved with a single token lookahead of "if the next token is the > keyword `while`, then the block body is the body of a do-while loop, else > it is the body of the block statement in a `do` expression". > > As for the EBNF, do-expressions could be parsed with a goal symbol of > either `+While` or `-While`, with do-while statements spec-wise effectively > being treated as do-expressions without an init part run repetitively, but > mandated to be statements. > > ```js > // Do expression > let foo = do { > foo(0) > }; > ``` ```js let foo = (foo(0)); ``` This seems as broken as the original. In both cases, unless I'm missing something, this is a TDZ violation when the right side evaluates foo. Mistake? > ```js > let tried = do try { > foo(0) > } catch (e) { > throw e > }; > ``` ```js let tried = (try { foo(0) } catch (e) { throw e }); ``` > ```js > // Do-while statement > let i = 0; > do { > foo(i) > } while (i++ < 10); > > // Combined: > let i = 0; > let foo9 = do do { > foo(i) // can have side effects, foo9 = foo(9) > } while (i++ < 10); > ``` ```js let i = 0; let foo9 = (do { foo(i) } while (i++ < 10)); ``` > Another example of where this could come in handy: simplifying > asynchronous code. > > ```js > function readConfig() { > fs.readFileAsync('config.json', 'utf8') > .then(JSON.parse) > .then(contents => do if (contents.unexpectedProperty) { > throw new Error('Bad property') // rejects the promise > } else { > doSomething(contents) > }) > .catch(err => process.domain.emit('err', error)) > } > ``` ```js ... .then(contents => (if (contents.unexpectedProperty) { ... })) ... ``` > ```js > // With only block statement > function readConfig() { > fs.readFileAsync('config.json', 'utf8') > .then(JSON.parse) > .then(contents => do { > if (contents.unexpectedProperty) { > throw new Error('Bad property') // rejects the promise > } else { > doSomething(contents) > } > }) > .catch(err => process.domain.emit('err', error)) > } > > // Without do-expressions > function readConfig() { > fs.readFileAsync('config.json', 'utf8') > .then(JSON.parse) > .then(contents => { > if (contents.unexpectedProperty) { > throw new Error('Bad property') // rejects the promise > } else { > doSomething(contents) > } > }) > .catch(err => process.domain.emit('err', error)) > } > ``` > > As you can see, the more general version does simplify things a little. > > Also, if-statements look better than long ternaries IMHO, and are less > repetitive than their counterpart, repeated assignment (us lazy typists...): > > ```js > let foo = do if (someCondition) { > value > } else if (someOtherCondition) { > value + 1 > } else if (someEdgeCase) { > addressEdgeCase(value) > } else { > value > } > ``` > ```js let foo = (if (someCondition) { ... }) ```