Mark S. Miller (2015-07-13T23:33:51.000Z)
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)
> };
>

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?


>
> let tried = do try {
>   foo(0)
> } catch (e) {
>   throw e
> };
>

let tried = (try { foo(0) } catch (e) { throw e });



>
> // 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);
> ```
>

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))
> }
>

...
.then(contents => (if (contents.unexpectedProperty) {
...
}))
...


>
> // 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
> }
> ```
>

let foo = (if (someCondition) {
...
})



>
> --
> Isiah Meadows
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
>


-- 
    Cheers,
    --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150713/df458e32/attachment-0001.html>
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) {
...
})
```