(Almost) everything is expression

# Dmitry Soshnikov (14 years ago)

(unfortunately Twitter is again doesn't fit for a more than 140 ch discussion, so moving here)

I'd like to still notice the possibility of accepting the "almost everything is expression" approach for JS. After Erlang I very often miss this possibility.

The idea is to make most of current complex statements as expressions.

Dave mentioned the proposal with do { ... } -- yeah, it's fine, but much nicer is to have "all of them" as expressions. CoffeeScript adopted this style as well.

Besides, I'd like to note, that the thing is not just "interesting theoretical stuff", but really is very convenient and useful in practice.

Examples:

  1. Switch-expression (as addition, I eliminated break)

let a = switch (foo) { case 10: 100; default: 200; };

  1. If-expression:

let a = if (foo) { print('a is foo'); foo; } else { // do some longer stuff };

We of course already have for years the expression sugar for this -- ? : operator, but it doesn't allow to conveniently have longer bodies of consequent and alternative nodes of if-expression.

  1. Try-expressions:

let a = try { // do dangerous stuff "ok value"; } catch (e) { "default value"; };

Another note -- I also made return optional (if need to exit from the middle -- we can use it; BTW, it's a lack of Erlang -- we can't exit from the middle, but should build our blocks in the needed way).

  1. What else? ... Perhaps some others.

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price.

P.S:

Regarding Dave's do { .. } -- we may omit do and just evaluate the block.

let a = { print('doing stuff'); 100; };

It's of course seems ambiguous with an object initialiser (at first glance), but it's only at first glance. Obviously there is a code inside to evaluate.

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 11:07, Dmitry Soshnikov wrote:

P.S:

Regarding Dave's do { .. } -- we may omit do and just evaluate the block.

let a = { print('doing stuff'); 100; };

It's of course seems ambiguous with an object initialiser (at first glance), but it's only at first glance. Obviously there is a code inside to evaluate.

Though, it can be visually really ambiguous with object initialisers in case of using labels inside the block:

let a = {a: 10; b: 20};

Nope, have to think more on this...

Dmitry.

# David Herman (14 years ago)

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price.

It's a serious price, though. Today if I write:

if (q) { ... }
else { ... }
(f())

then ASI kicks in after the else body. If we make if-statements into expressions, then either the above becomes a single expression, which is a serious and subtle backwards-incompatible change, or we define lookahead restrictions on ExpressionStatement, and introduce a refactoring hazard:

x = if (q) { ... }
    else { ... }
(f())                 // oops, this is now a parameter list on the RHS of the assignment!

I'm not positive, but that seems like a serious issue to me.

Though, it can be visually really ambiguous with object initialisers in case of using labels inside the block:

A JS grammar needs to be formally unambiguous, so it requires very careful specification. Syntax design for JS is very tricky.

Nope, have to think more on this...

You might want to take a look at this:

http://wiki.ecmascript.org/doku.php?id=strawman:block_vs_object_literal
# Dmitry Soshnikov (14 years ago)

On 11.11.2011 11:43, David Herman wrote:

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price. It's a serious price, though. Today if I write:

 if (q) { ... }
 else { ... }
 (f())

then ASI kicks in after the else body. If we make if-statements into expressions, then either the above becomes a single expression, which is a serious and subtle backwards-incompatible change, or we define lookahead restrictions on ExpressionStatement, and introduce a refactoring hazard:

 x = if (q) { ... }
     else { ... }
 (f())                 // oops, this is now a parameter list on the RHS of the assignment!

I'm not positive, but that seems like a serious issue to me.

Yes, all this relatively true, but personally I don't see the big issue. In practice we already have such a case e.g. for FD (function declaration) vs. FE (function expression).

The former doesn't require semicolon, the later does. Though, in the later case (FE), today most of programmers put explicit semicolon to avoid problems with scripts minimizing. From this viewpoint it's not a big price, since even now the programmers are already used to such cases.

Regarding old code it's also not the issue since there is no such old code, it's a syntax error. And even if a user will refactor code (to make it look shorter and elegantly), she should be aware about this case (again -- just like with FD and FE -- users are aware about it):

Was:

var x;

if (q) { x = 10; } else { x = 20; }

Becomes:

let x = if (q) { 10; } else { 20; };

Nope, have to think more on this... You might want to take a look at this:

 http://wiki.ecmascript.org/doku.php?id=strawman:block_vs_object_literal

Yep, I've seen it before briefly; will check it more precisely later, thanks.

Dmitry.

# François REMY (14 years ago)

<aside note>

let x = q ? 10 : 20;

Why we're reinventing the wheel here is up to me.

</aside>

-----Message d'origine---

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 13:26, François REMY wrote:

<aside note>

let x = q ? 10 : 20;

Why we're reinventing the wheel here is up to me.

</aside>

I noted it in the initial letter. Yes, we have the sugar for this particular case for years (the ternary operator). But also with this I mention that it doesn't allow conveniently handle complex bodies of consequent and alternative nodes of an if-expression. Please re-read my initial letter.

Once again, the idea is to have in addition for all the statement parts the same expression parts (not only for if-statement, so sorry, I don't buy your "reinventing the wheel").

And since this is exactly the addition, but not the replacement, it's still possible to use statement forms (w/o explicit semicolon). But if you need an expression form, use it. The same as with FD and FE.

Dmitry.

# David Bruant (14 years ago)

It reminds me of something I was thinking about recently. A very common pattern in JS is:

(function(global){ var private1 = 'a'; // ... global.something = 11; })(this)

block-scoped variables allow a light-weigth version of this pattern:

{ let private1 = 'a';

// |this| is the global object already, but if people feel uncomfortable with // reading code with |this|, they can always create an alias: const global = this;

// ... global.something = 11; }

So I thought we could get rid of the (function(){...}()) pattern for good. But there is also this pattern:

var C = (function(global){ var something;

 // ...

 return something;

})(this);

which I thought could be turned into:

var C = { let something;

 // |this| is the global object
 // ...

}

But it does not have a return value and as noted in your second message, there seems to be an ambiguity with object initializers (not only visual but formal). The 'do expressions' solution sounds like a good idea, but visually reminds of a do-while loop.

I don't really have a better proposition yet, but i'm looking forward to seeing any solution that will allow to replace all "(function(global){...})(this);"

David

Le 11/11/2011 08:07, Dmitry Soshnikov a écrit :

# François REMY (14 years ago)

I didn't read your first mail, I've to acknowledge. That doesn't change the fact the sample was reinventing the wheel.

BTW,your samples all suffer from a big ambuiguity that I think is unresolvable.

let a = if (foo) {
   print('a is foo');
   foo;
} else {
   // do some longer stuff
};

How do you know "foo" is an expression that should be assigned to "a" and that "print('a...')" is not?

In ECMAScript, each statement returns a value. There's no way to find out if the statement is a "normal" statement or a "value" statement. To my understanding, there's no.

let a = try {
    if(test()) {
        translate("test was true")
    } else {
        translate("test was false")
    }
} catch(ex) {
    translate("test has raised an exception")
}

Secondly, is it worth modifying the UA's compilers and JS syntax? What's what you gain, in terms of readability, in terms of facility, ... ?

PS: I don't know what is the "do" syntax you reference but I guess it solves the ambiguity problem by creating a kind of "block lambda". Value statements can be recognized by a "return" statement inside the block.

let a = do {
    if(test()) {
        return translate("...");
    } else {
        return translate("...");
    }
}

In such case "do { ... }" is just a sugar for (function() { ...})(). It could be used to make more beautiful the "module" pattern used in many codes now.

-----Message d'origine---

# Axel Rauschmayer (14 years ago)

which I thought could be turned into:

var C = { let something;

// |this| is the global object // ... }

But it does not have a return value and as noted in your second message, there seems to be an ambiguity with object initializers (not only visual but formal). The 'do expressions' solution sounds like a good idea, but visually reminds of a do-while loop.

I don't really have a better proposition yet, but i'm looking forward to seeing any solution that will allow to replace all "(function(global){...})(this);"

IIRC: Block lambdas.

var C = {|| // note the double pipe let something;

// |this| is the global object // ...

return something; }

With a single expression, you don’t even have to return:

var C = {|| // note the double pipe foo() }

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 14:37, David Bruant wrote:

Hi,

It reminds me of something I was thinking about recently. A very common pattern in JS is:

(function(global){ var private1 = 'a'; // ... global.something = 11; })(this)

block-scoped variables allow a light-weigth version of this pattern:

{ let private1 = 'a';

// |this| is the global object already, but if people feel uncomfortable with // reading code with |this|, they can always create an alias: const global = this;

// ... global.something = 11; }

So I thought we could get rid of the (function(){...}()) pattern for good. But there is also this pattern:

var C = (function(global){ var something;

// ...

return something;

})(this);

which I thought could be turned into:

var C = { let something;

// |this| is the global object
// ...

}

But it does not have a return value and as noted in your second message, there seems to be an ambiguity with object initializers (not only visual but formal). The 'do expressions' solution sounds like a good idea, but visually reminds of a do-while loop.

I don't really have a better proposition yet, but i'm looking forward to seeing any solution that will allow to replace all "(function(global){...})(this);"

Yep, exactly. Block-expressions (w/ return values) are in many languages. And it's really often convenient. E.g. in the same Erlang (sorry for mentioning it too often), you may also use imminently applied function:

Foo = (fun() -> % do stuff 100 end)(),

But nobody do this there, since e.g. there is a block-expression:

Foo = begin % do stuff 100 end,

And the main thing also, that we can use internal variable there. It's of course can be achieved with let blocks as you show above, but such blocks don't allow to assign the result to the outside. Actually, it can be OK, we may assigned the value inside the block itself:

let foo;

{ let x = 10; foo = x; }

But if the block is long, it seems more convenient to see that the block assigns something in the head of the block, but not in its body:

let foo = { // do stuff 100; }

Anyway, the blocks are just "one of" the cases. The proposal is to have "all of them" as expressions as well, e.g. switch, if, try, perhaps while etc.

Dmitry.

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 14:44, François REMY wrote:

I didn't read your first mail, I've to acknowledge. That doesn't change the fact the sample was reinventing the wheel.

I still don't see how your this sentence helps taking into account that I myself noted this case, sorry.

BTW,your samples all suffer from a big ambuiguity that I think is unresolvable.

let a = if (foo) { print('a is foo'); foo; } else { // do some longer stuff };

How do you know "foo" is an expression that should be assigned to "a" and that "print('a...')" is not?

Please concentrate on the main problem. Don't consider now some small non-essential issues of examples. In this proposal and the exact example it doesn't matter what is foo and to what it can be assigned.

In ECMAScript, each statement returns a value.

At implementation level. And it's good. It means the implementation will not require much modifications, since e.g. Block statement (12.1, ES5.1) returns as a result the value of last evaluated statement. So the thing is just allow this result to be assigned to the LHS. In other words, the ability to make the block and other statements as RHS, e.g. to make them expressions.

There's no way to find out if the statement is a "normal" statement or a "value" statement. To my understanding, there's no.

let a = try { if(test()) { translate("test was true") } else { translate("test was false") } } catch(ex) { translate("test has raised an exception") }

Yes, this is completely OK. No worries for implementation. The only thing is to allow this try be RHS of the assignment. Now it's a syntax error.

Secondly, is it worth modifying the UA's compilers and JS syntax? What's what you gain, in terms of readability, in terms of facility, ... ?

I think it worth. Again, after Erlang I felt inconvenient that I have to (1) declare a var above and (2) provide assigned to the var in two (e.g.) if branches. I want to write only one assignment and to tell that the result of the assignment is the result of evaluating this complex expression. It's more convenient than to look inside long block body and to understand whether it assigns to outer var or not.

PS: I don't know what is the "do" syntax you reference but I guess it solves the ambiguity problem by creating a kind of "block lambda". Value statements can be recognized by a "return" statement inside the block.

let a = do { if(test()) { return translate("..."); } else { return translate("..."); } }

In such case "do { ... }" is just a sugar for (function() { ...})(). It could be used to make more beautiful the "module" pattern used in many codes now.

Yep. Though, we have already such a sugar for immediately applied lambda -- it's a let-statement:

let (a = 10, b = 20) { // do stuff }

in many implementations are just a sugar of

(function (a, b) { // do stuff })(10, 20);

Dmitry.

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 14:52, Axel Rauschmayer wrote:

which I thought could be turned into:

var C = { let something;

// |this| is the global object
// ...

}

But it does not have a return value and as noted in your second message, there seems to be an ambiguity with object initializers (not only visual but formal). The 'do expressions' solution sounds like a good idea, but visually reminds of a do-while loop.

I don't really have a better proposition yet, but i'm looking forward to seeing any solution that will allow to replace all "(function(global){...})(this);"

IIRC: Block lambdas.

var C = {|| // note the double pipe let something;

// |this| is the global object
// ...

return something;

}

With a single expression, you don’t even have to return:

var C = {|| // note the double pipe foo() }

This contradicts TCP though. E.g. Ruby supports it and there we can't return from blocks, since block wrapping should not be treated as a function.

OTOH, as I noted in the initial letter, it can be convenient to return from the middle of such a block with exactly return statement. But, it's again contradicts TCP, and return should exit exactly execution context, but not just a block. Exactly for this it's used in Ruby, when begin inside a function you may exit directly from the function using return from the block passed to a higher-order function, e.g. each or arrays.

Besides, if to accept this Ruby's blocks, then in common case -- e.g. as shorted notation for functional arguments.

Dmitry.

# Axel Rauschmayer (14 years ago)

IIRC: Block lambdas.

var C = {|| // note the double pipe let something;

// |this| is the global object // ...

return something; }

This contradicts TCP though. E.g. Ruby supports it and there we can't return from blocks, since block wrapping should not be treated as a function.

True. My bad. It should work if the return is removed.

Related to the discussion: harmony:completion_reform

# François REMY (14 years ago)

I think you strongly underestimate the "distinction" problem. It's not possible to make any difference between the "foo" statement and the "print" statement of your sample, from the compiler point of view. Why would the "foo" statement be the one chosen as a return value for the "if" block? It's completelty unclear to me. If there's no way to tell what the "return statement" of the block is, there's no way to implement your proposal. It's not because the goal of the code seems clear to an human it will be equally clear to a compiler, because it's a completely different story there.

Let me introduce a sample :

let x = if(test) {
    translate("test was true");
} else {
    translate("test was false");
}

vs

let x = if(test) {
    print("test was true");
    a+b;
} else {
    print("test was false");
    a-b;
}

The "print" and the "translate" statement are identical, but they don't have the same "translation" since only one becomes an assignation. The "do" syntax solves this problem with a "return" statement, but you can't imagine that for each statement, like a "if", right?

My guess is that the value of a statement (in your proposed syntax) could be the return value of the latest statement evaluated in the block. The problem is that it may be unknown at compilation time... or isn't it?

-----Message d'origine---

# gaz Heyes (14 years ago)

11/11/2011 08:07, Dmitry Soshnikov :

Hi,

(unfortunately Twitter is again doesn't fit for a more than 140 ch discussion, so moving here)

I'd like to still notice the possibility of accepting the "almost everything is expression" approach for JS. After Erlang I very often miss this possibility.

The idea is to make most of current complex statements as expressions.

Dave mentioned the proposal with do { ... } -- yeah, it's fine, but much nicer is to have "all of them" as expressions. CoffeeScript adopted this style as well.

Besides, I'd like to note, that the thing is not just "interesting theoretical stuff", but really is very convenient and useful in practice.

Examples:

  1. Switch-expression (as addition, I eliminated break)

let a = switch (foo) { case 10: 100; default: 200; };

  1. If-expression:

let a = if (foo) { print('a is foo'); foo; } else { // do some longer stuff };

I think this is a bad idea, the difference between a block and expression is difficult enough to decide (from a sandboxing perspective). It would also make missing semi-colons continue the expression and make it really hard for the developer to track down the error and cause unintended behaviour. It adds to the complexity of deciding if a curly is a block or literal and if a "/" is a divide or regexp (which is difficult enough in JavaScript). How on earth would this be backwards compatible too?

We of course already have for years the expression sugar for this -- ? : operator, but it doesn't allow to conveniently have longer bodies of consequent and alternative nodes of if-expression.

I'm pretty sure it does: 0?0:0?0:2

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 15:48, François REMY wrote:

I think you strongly underestimate the "distinction" problem. It's not possible to make any difference between the "foo" statement and the "print" statement of your sample, from the compiler point of view. Why would the "foo" statement be the one chosen as a return value for the "if" block? It's completelty unclear to me. If there's no way to tell what the "return statement" of the block is, there's no way to implement your proposal.

Currently the last evaluated statement of a block is the result of the block (see 12.1). That's said, manual return contradicts TCP, so I noted it only as additional topic to consider. The main feature -- just the last evaluated statement is the result of the block -- as it's not at implementation level.

It's not because the goal of the code seems clear to an human it will be equally clear to a compiler, because it's a completely different story there.

Let me introduce a sample :

let x = if(test) { translate("test was true"); } else { translate("test was false"); }

vs

let x = if(test) { print("test was true"); a+b; } else { print("test was false"); a-b; }

The "print" and the "translate" statement are identical, but they don't have the same "translation" since only one becomes an assignation. The "do" syntax solves this problem with a "return" statement, but you can't imagine that for each statement, like a "if", right?

I don't see the problem here. In first example the result of the if is the result of translate("test was true"); in case if test is evaluated to true (with any acceptable boolean coercions), or it's the result of evaluating of translate("test was false"); in other case.

My guess is that the value of a statement (in your proposed syntax) could be the return value of the latest statement evaluated in the block. The problem is that it may be unknown at compilation time... or isn't it?

How the compilation time is related here? Once again, we need only the ability to assign results of statement evaluations (thus, automatically turning them into expressions). Since statements at implementation level return values, the only change is to fix assignment -- to allow those statement on the RHS.

Dmitry.

# Andreas Rossberg (14 years ago)

On 11 November 2011 12:48, François REMY <fremycompany_pub at yahoo.fr> wrote:

I think you strongly underestimate the "distinction" problem. It's not possible to make any difference between the "foo" statement and the "print" statement of your sample, from the compiler point of view. Why would the "foo" statement be the one chosen as a return value for the "if" block? It's completelty unclear to me.

JavaScript already has the notion of "completion value" of a statement sequence, which defines exactly that. It is used to determine the result of eval calls. Unfortunately, the completion value is currently determined dynamically, but there is a proposal for correcting that behaviour, see harmony:completion_reform.

Using this to define implicit return values of blocks or functions was one idea behind that proposal. Effectively, this just generalizes the comma operator (which is redundant in the same way ?: is).

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 16:03, gaz Heyes wrote:

11/11/2011 08:07, Dmitry Soshnikov :

    Hi,

    (unfortunately Twitter is again doesn't fit for a more than
    140 ch discussion, so moving here)

    I'd like to still notice the possibility of accepting the
    "almost everything is expression" approach for JS. After
    Erlang I very often miss this possibility.

    The idea is to make most of current complex statements as
    expressions.

    Dave mentioned the proposal with `do { ... }` -- yeah, it's
    fine, but much nicer is to have "all of them" as expressions.
    CoffeeScript adopted this style as well.

    Besides, I'd like to note, that the thing is not just
    "interesting theoretical stuff", but really is very convenient
    and useful in practice.

    Examples:

    1. Switch-expression (as addition, I eliminated `break`)

    let a = switch (foo) {
     case 10: 100;
     default: 200;
    };

    2. If-expression:

    let a = if (foo) {
     print('a is foo');
     foo;
    } else {
     // do some longer stuff
    };

I think this is a bad idea,

Yeah, "right", this is why it's widely implemented and adopted by many languages.

the difference between a block and expression is difficult enough to decide (from a sandboxing perspective). It would also make missing semi-colons continue the expression and make it really hard for the developer to track down the error and cause unintended behaviour. It adds to the complexity of deciding if a curly is a block or literal and if a "/" is a divide or regexp (which is difficult enough in JavaScript). How on earth would this be backwards compatible too?

Yes, we have to understand which exactly form is useful for example block-expressions. But in general I'm talking about all of those statements, not just a simple blocks. Though of course example blocks are used as parts of e.g. if-statements, etc.

The proposal is very simple -- to allow these statements to stand on the RHS. Regarding implementations, the statements already return values, so it shoudn't be hard to adopt it.

    We of course already have for years the expression sugar for
    this -- ? : operator, but it doesn't allow to conveniently
    have longer bodies of consequent and alternative nodes of
    if-expression.

I'm pretty sure it does: 0?0:0?0:2

I guess you see the point ;)

Dmitry.

# Mike Samuel (14 years ago)

2011/11/11 David Herman <dherman at mozilla.com>:

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price.

It's a serious price, though. Today if I write:

if (q) { ... }    else { ... }    (f())

then ASI kicks in after the else body. If we make if-statements into expressions, then either the above becomes a single expression, which is a serious and subtle backwards-incompatible change, or we define lookahead restrictions on ExpressionStatement, and introduce a refactoring hazard:

x = if (q) { ... }        else { ... }    (f())                 // oops, this is now a parameter list on the RHS of the assignment!

I'm not positive, but that seems like a serious issue to me.

There seem to be two separable issues here: (1) being able to use conditions, loops, etc. where expressions can occur. (2) return elision

I also find this a serious problem for (1) and there are less serious changes of behavior when the following token is ('[', '+', '-').

It can't possibly be changed by mucking with precedence levels or reworking CallExpression because of

new if (c) s1 else s2 (f())

but if instead of changing PrimaryExpression, you change the grouping operator from '(' <Expression> ')'

to '(' (lookahead not in '{', 'function') <StatementList> ')'

or even with let-scoped functions declarations '(' (lookahead not in '{', 'function') <Program> ')'

and change semicolon insertion to allow insertion before a ')' which should not affect for(;;).

# David Herman (14 years ago)

On Nov 11, 2011, at 3:48 AM, François REMY wrote:

I think you strongly underestimate the "distinction" problem. ... It's completelty unclear to me. If there's no way to tell what the "return statement" of the block is, there's no way to implement your proposal.

It's actually quite easy to implement Dmitry's proposal, because it's already specified by the ECMAScript semantics! All statements can produce completion values. You can test this yourself: take any statement, quote it as a string, and put it in eval, and you'll get a value (if it doesn't loop infinitely, exit the program, or throw an exception, of course).

As Andreas said, there's a subtler issue of whether there's a simple structure to value-producing substatements, and there's a few problematic cases, but there's already a plan to clean that up at

http://wiki.ecmascript.org/doku.php?id=harmony:completion_reform

But that's mostly a secondary issue, to keep things more regular and to be more compatible with tail calls.

To answer your specific example:

let a = if (foo) { print('a is foo'); foo; } else { // do some longer stuff };

How do you know "foo" is an expression that should be assigned to "a" and that "print('a...')" is not?

Because the then-branch of the if is a block, and the completion value of a block is specified in ECMAScript to be the last completion value produced by its body statements.

# Mark S. Miller (14 years ago)

Insist on enclosing parens, since "(" introductory-token is not otherwise legal

let a = (switch (foo) { case 10: 100; default: 200; });

  1. If-expression:

let a = (if (foo) { print('a is foo'); foo; } else { // do some longer stuff });

  1. Try-expressions:

let a = (try { // do dangerous stuff "ok value"; } catch (e) { "default value"; });

let a = ({ print('doing stuff'); 100; });

Even the last is now easily unambiguous.

# Neil Eades (14 years ago)

On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote:

Insist on enclosing parens, since "(" introductory-token is not otherwise legal

let a = ({

print('doing stuff'); 100; });

Even the last is now easily unambiguous.

And is this not clearer than

let a = {||

print('doing stuff'); 100; };

# gaz Heyes (14 years ago)

On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote:

let a = ({

print('doing stuff'); 100; });

How do you know the difference between a blank block statement and a object literal? Surely it becomes an expression once an assignment occurs anyway.

# Mark S. Miller (14 years ago)

On Fri, Nov 11, 2011 at 7:40 AM, gaz Heyes <gazheyes at gmail.com> wrote:

On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote:

let a = ({

print('doing stuff'); 100; });

How do you know the difference between a blank block statement and a object literal? Surely it becomes an expression once an assignment occurs anyway.

Doh! Sorry, I completely mis-thought that. Nevermind.

# Brendan Eich (14 years ago)

On Nov 10, 2011, at 11:07 PM, Dmitry Soshnikov wrote:

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price.

No, it is a runtime incompatibility that shifts meaning, without errors.

switch (x) { case 1: (function (){return 42}); break; default: (function (){return 99}); } (a[i].b()).c(d)

The switch is now the callee expression in a call taking one actual parameter, a[i].b().

The same can happen with leading [, unary +/-, and / as regexp delimiter -- any lexeme that can both start a statement and continue an expression.

P.S:

Regarding Dave's do { .. } -- we may omit do and just evaluate the block.

let a = { print('doing stuff'); 100; };

It's of course seems ambiguous with an object initialiser (at first glance), but it's only at first glance. Obviously there is a code inside to evaluate.

I worked on this, based on ideas from Breton Slivka and Doug Crockford. Please see

strawman:arrow_function_syntax

and

esdiscuss/2011-June/015568

This is not going to fly in a grammar that we validate using LR(1) parsing.

Block-lambdas require {|| at least to defer evaluation until invocation, whereas any block-expression would be immediately evaluated. This could be a point of confusion.

Altogether, this says Dave's 'do' proposal is better because EIBTI.

# Brendan Eich (14 years ago)

On Nov 11, 2011, at 2:52 AM, Axel Rauschmayer wrote:

which I thought could be turned into:

var C = { let something;

// |this| is the global object // ... }

But it does not have a return value and as noted in your second message, there seems to be an ambiguity with object initializers (not only visual but formal). The 'do expressions' solution sounds like a good idea, but visually reminds of a do-while loop.

I don't really have a better proposition yet, but i'm looking forward to seeing any solution that will allow to replace all "(function(global){...})(this);"

IIRC: Block lambdas.

var C = {|| // note the double pipe let something;

// |this| is the global object // ...

return something;

That would attempt to return from the enclosing function or be an early error if there is no enclosing function.

Principle of equivalence, e ==== {|| e}(), applies.

# David Herman (14 years ago)

On Nov 11, 2011, at 8:19 AM, Mark S. Miller wrote:

On Fri, Nov 11, 2011 at 7:40 AM, gaz Heyes <gazheyes at gmail.com> wrote: On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote: let a = ({

print('doing stuff'); 100; });

How do you know the difference between a blank block statement and a object literal? Surely it becomes an expression once an assignment occurs anyway.

Doh! Sorry, I completely mis-thought that. Nevermind.

Your idea of mandatory parens is still valid (if, IMO, a bit unsatisfyingly verbose) for most statement forms. It's only the block-statement-expression that doesn't work. Hence my do-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions

or Brendan's subtly-disambiguated-block-statement-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:block_vs_object_literal

If Brendan's idea can be made to work, and it's not too confusing, I'm pretty sure I'd prefer it over do-expressions. You could simply write:

let a = {
    print('doing stuff');
    100
};

How gorgeous is that?

But I suspect as we work on evolving the syntax of object literals, it'll get harder to keep them disambiguated. For example, is this:

let a = {
    foo(x)
    {
        alert(x)
    }
}

...equivalent to this?

let a = {
    foo: function(x)
    {
        alert(x);
    }
};

...or this?

let a = {
    foo(x);
    {
        alert(x);
    }
};

So I just don't know if it's feasible.

# Allen Wirfs-Brock (14 years ago)

On Nov 11, 2011, at 7:39 AM, Neil Eades wrote:

On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote: Insist on enclosing parens, since "(" introductory-token is not otherwise legal

let a = ({

print('doing stuff'); 100; });

Even the last is now easily unambiguous.

And is this not clearer than

let a = {||

print('doing stuff'); 100; };

that needs to be:

let a = {||

print('doing stuff'); 100; }(); //<------- note the ()

other wise the value of a is a function, rather than 100

# Mark S. Miller (14 years ago)

On Fri, Nov 11, 2011 at 9:13 AM, David Herman <dherman at mozilla.com> wrote: [...]

Hence my do-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions

or Brendan's subtly-disambiguated-block-statement-expressions:

strawman:block_vs_object_literal

If Brendan's idea can be made to work, and it's not too confusing, I'm pretty sure I'd prefer it over do-expressions.

[...]

Interesting. But I think I prefer do expressions over making JS parsing that much more subtle. JS is already much to hard to parse, and other accepted changes are already making it even more so.

# Allen Wirfs-Brock (14 years ago)

On Nov 11, 2011, at 9:13 AM, David Herman wrote:

On Nov 11, 2011, at 8:19 AM, Mark S. Miller wrote:

On Fri, Nov 11, 2011 at 7:40 AM, gaz Heyes <gazheyes at gmail.com> wrote: On 11 November 2011 15:33, Mark S. Miller <erights at google.com> wrote: let a = ({

print('doing stuff'); 100; });

How do you know the difference between a blank block statement and a object literal? Surely it becomes an expression once an assignment occurs anyway.

Doh! Sorry, I completely mis-thought that. Nevermind.

Your idea of mandatory parens is still valid (if, IMO, a bit unsatisfyingly verbose) for most statement forms. It's only the block-statement-expression that doesn't work. Hence my do-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions

or Brendan's subtly-disambiguated-block-statement-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:block_vs_object_literal

If Brendan's idea can be made to work, and it's not too confusing, I'm pretty sure I'd prefer it over do-expressions.

I think you''d be making a mistake to go in that direction. do-expression is a very good solution that fits very well into the language and does not introduce any new readability ambiguities (coining a phrase, a readability ambiguity is a construct that is not formally ambiguous according to the grammar but which will require mental effort by a human reader to appropriate classify).

You could simply write:

let a = {
    print('doing stuff');
    100
};

How gorgeous is that?

not very... when I see a = { I immediate have to start thinking, is this think that follows the =? Is it block expression? is it an object literal? is it a block lambda?

but if I see any of: let a = do {... let a = {|| ... let a = { ... I immediately know what follows. That is gorgeous...

But I suspect as we work on evolving the syntax of object literals, it'll get harder to keep them disambiguated. For example, is this:

we're already there ...

let a = {
    foo(x)
    {
        alert(x)
    }
}

...equivalent to this?

let a = {
    foo: function(x)
    {
        alert(x);
    }
};

...or this?

let a = {
    foo(x);
    {
        alert(x);
    }
};

So I just don't know if it's feasible.

And being technically feasible does not make it desirable. Just remember the phrase: "readability ambiguity"

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 20:36, Brendan Eich wrote:

On Nov 10, 2011, at 11:07 PM, Dmitry Soshnikov wrote:

Brendan and Dave mention explicit semicolon. Yes, it's seems so by the grammar (though, have to check more precisely), but it can be acceptable price. No, it is a runtime incompatibility that shifts meaning, without errors.

switch (x) { case 1: (function (){return 42}); break; default: (function (){return 99}); } (a[i].b()).c(d)

The switch is now the callee expression in a call taking one actual parameter, a[i].b().

The same can happen with leading [, unary +/-, and / as regexp delimiter -- any lexeme that can both start a statement and continue an expression.

If we accept expression forms as the addition, I don't see the issue here. In this case, switch shouldn't be treated as a special expression, but should behave as the before.

If in contrast switch stands in the expression position, then it returns its evaluated result.

It's just like FD and FE -- the later is determined only by the position at which it stands -- if a function stands at the expression position, then it's a FE, otherwise, it's FD. The same is here.

P.S:

Regarding Dave's do { .. } -- we may omit do and just evaluate the block.

let a = { print('doing stuff'); 100; };

It's of course seems ambiguous with an object initialiser (at first glance), but it's only at first glance. Obviously there is a code inside to evaluate. I worked on this, based on ideas from Breton Slivka and Doug Crockford. Please see

strawman:arrow_function_syntax

and

esdiscuss/2011-June/015568

Yup. I of course read before this proposal. And it's pity it's not approved, I support them, so of course the problem with parser should be considered, I agree (moreover, if you look the archive for a one year

# David Herman (14 years ago)

On Nov 11, 2011, at 9:50 AM, Allen Wirfs-Brock wrote:

do-expression is a very good solution

Why thank you! ;-)

How gorgeous is that?

not very...

but if I see any of: let a = do {... let a = {|| ... let a = { ... I immediately know what follows. That is gorgeous...

I'm not sure I buy that let x = do { f(); 12 } is prettier than let x = { f(); 12 } but I do agree that it's less subtle, given the existence of object literals. And do is as short a keyword as you can get in JS, so it's a pretty minimal amount of extra noise.

So I just don't know if it's feasible.

And being technically feasible does not make it desirable.

Of course. I didn't just mean "technically feasible," I meant I don't know if it's feasible from a design perspective.

Just remember the phrase: "readability ambiguity"

Fair enough. Personally, I think there are situations where a disambiguation that's technically subtle can actually be totally intuitive to the human eye and doesn't cause readability problems in practice. But I agree with you that this is not likely one of those cases. There's just too much overlap between what can go inside an object literal and what can go inside block statements.

# gaz Heyes (14 years ago)

On 11 November 2011 17:13, David Herman <dherman at mozilla.com> wrote:

Your idea of mandatory parens is still valid (if, IMO, a bit unsatisfyingly verbose) for most statement forms. It's only the block-statement-expression that doesn't work. Hence my do-expressions

It should also apply to function expressions IMO too as there are instances especially in for loops where there is confusion between the two.

# gaz Heyes (14 years ago)

On 11 November 2011 18:01, Dmitry Soshnikov <dmitry.soshnikov at gmail.com>wrote:

var foo = { // do stuff 100; };

What would be the result of a labelled statement? You'd need labels to work within expressions since you'd probably want to do: x=loop:for(i=0;i<10;i++){ }

but then what if you do: x=1/loop:for(i=0;i<10;i++){ }

Is 1 divided by the result of the for loop or is it divided by undefined?

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 21:13, David Herman wrote:

On Nov 11, 2011, at 8:19 AM, Mark S. Miller wrote:

On Fri, Nov 11, 2011 at 7:40 AM, gaz Heyes <gazheyes at gmail.com <mailto:gazheyes at gmail.com>> wrote:

On 11 November 2011 15:33, Mark S. Miller <erights at google.com
<mailto:erights at google.com>> wrote:

        let a = ({

         print('doing stuff');
         100;
        });


How do you know the difference between a blank block statement
and a object literal? Surely it becomes an expression once an
assignment occurs anyway.

Doh! Sorry, I completely mis-thought that. Nevermind.

Your idea of mandatory parens is still valid (if, IMO, a bit unsatisfyingly verbose) for most statement forms. It's only the block-statement-expression that doesn't work. Hence my do-expressions:

strawman:do_expressions

or Brendan's subtly-disambiguated-block-statement-expressions:

strawman:block_vs_object_literal

If Brendan's idea can be made to work, and it's not too confusing, I'm pretty sure I'd prefer it over do-expressions. You could simply write:

let a = {
    print('doing stuff');
    100
};

If we write so in e.g. if-expression (which actually uses the same block), then by logic we should write the same in a simple (and the same) block:

// conditional evaluation of a let a = if (true) { 10 } else { 20 }

// unconditional evaluation of a let a = {10}

(BTW, everyone already now may test it -- as Dave noted, you may use eval to get the completion type of a statement; moreover, you may write a simple parser which just replaces all such expressions with evals -- not for production sure, but for tests: let a = eval('if (true) {10} else {20}'); console.log(a) -- 10)

OTOH, if there will be unresolvable (?) issues with grammar, etc, we may of course adopt a special syntax of eval'ing such simple blocks, unfortunately regardless, that e.g. in if which uses the same block we may write it easily and directly.

How gorgeous is that?

It's normal and consistent with other blocks, I'd say.

Dmitry.

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 22:25, gaz Heyes wrote:

On 11 November 2011 18:01, Dmitry Soshnikov <dmitry.soshnikov at gmail.com <mailto:dmitry.soshnikov at gmail.com>> wrote:

var foo = {
 // do stuff
 100;
};

What would be the result of a labelled statement? You'd need labels to work within expressions since you'd probably want to do: x=loop:for(i=0;i<10;i++){ }

but then what if you do: x=1/loop:for(i=0;i<10;i++){ }

Is 1 divided by the result of the for loop or is it divided by undefined?

This is why the topic is called "(Almost) everything...". In general case we may not include this case with label-statement into proposal, since the construction you wrote isn't practice IMO. But, we should consider also theoretical things and this case it can be better to avoid the case at all then to solve it -- especially if there is no much profit in practice from it. Though, if we can manage it, why not?

Dmitry.

# gaz Heyes (14 years ago)

On 11 November 2011 18:29, Dmitry Soshnikov <dmitry.soshnikov at gmail.com>wrote:

This is why the topic is called "(Almost) everything...". In general case we may not include this case with label-statement into proposal, since the construction you wrote isn't practice IMO. But, we should consider also theoretical things and this case it can be better to avoid the case at all then to solve it -- especially if there is no much profit in practice from it. Though, if we can manage it, why not?

Another thing isn't a block statement a no-op? So : x={};

x wouldn't be undefined or anything since a blank block statement shouldn't return anything. Then if it returns undefined then how would a block statement actually work: {}/1

You'd have to make a block statement return undefined to make sense.

So as you can see you are adding complexity to the syntax in both the developer level and the sandboxing level. There are a ton of problems this would create.

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 22:48, gaz Heyes wrote:

On 11 November 2011 18:29, Dmitry Soshnikov <dmitry.soshnikov at gmail.com <mailto:dmitry.soshnikov at gmail.com>> wrote:

This is why the topic is called "*(Almost)* everything...". In
general case we may not include this case with label-statement
into proposal, since the construction you wrote isn't practice
IMO. But, we should consider also theoretical things and this case
it can be better to avoid the case at all then to solve it --
especially if there is no much profit in practice from it. Though,
if we can manage it, why not?

Another thing isn't a block statement a no-op? So : x={};

x wouldn't be undefined or anything since a blank block statement shouldn't return anything.

Obviously here it must be an object initialiser.

We basically may even restrict to use these statements as expressions only in some local expressions domain. E.g. only for assignments. There are alternatives which we shouldn't afraid to consider.

Then if it returns undefined then how would a block statement actually work: {}/1

You'd have to make a block statement return undefined to make sense.

So as you can see you are adding complexity to the syntax in both the developer level and the sandboxing level. There are a ton of problems this would create.

I don't see it. I started the proposal not because I thought out "some interesting stuff to discuss", but with concrete practical reason. I referred to Erlang where I everyday with much more convenience than in current JS may use all those complex expression on RHS.

Dmitry.

# David Herman (14 years ago)

How gorgeous is that?

It's normal and consistent with other blocks, I'd say.

Sorry, that was an (American?) English colloquialism -- a rhetorical question meaning "that's gorgeous!"

# Dmitry Soshnikov (14 years ago)

On 11.11.2011 23:44, David Herman wrote:

How gorgeous is that? It's normal and consistent with other blocks, I'd say. Sorry, that was an (American?) English colloquialism -- a rhetorical question meaning "that's gorgeous!"

<offtopic>

And what does it mean? :) I translated it as "how do you like it, ah?" or "isn't it just nice?". On what I say, "yeah it's nice and consistent with other blocks".

Anyway, thanks for noticing, basically I know English (American ;)), though not such interesting colloquialisms.

</offtopic>

Dmitry.

# David Herman (14 years ago)

I would translate "How X is that?" as "that is very X!" :)

# Mike Samuel (14 years ago)

2011/11/11 David Herman <dherman at mozilla.com>:

On Nov 11, 2011, at 3:48 AM, François REMY wrote:

I think you strongly underestimate the "distinction" problem. ... It's completelty unclear to me. If there's no way to tell what the "return statement" of the block is, there's no way to implement your proposal.

It's actually quite easy to implement Dmitry's proposal, because it's already specified by the ECMAScript semantics! All statements can produce completion values. You can test this yourself: take any statement, quote it as a string, and put it in eval, and you'll get a value (if it doesn't loop infinitely, exit the program, or throw an exception, of course).

If statements as expressions goes forward, we should look into tweaking completion values.

IMHO, a code maintainer who sees

resource = ..., foo(resource)

would expect to be able to wrap the use of resource in a try finally thus

resource = ..., (try { foo(resource) } finally { release(resource) })

without changing the completion value of the expression.

# David Herman (14 years ago)

On Nov 11, 2011, at 2:51 PM, Mike Samuel wrote:

If statements as expressions goes forward, we should look into tweaking completion values.

IMHO, a code maintainer who sees

resource = ..., foo(resource)

would expect to be able to wrap the use of resource in a try finally thus

resource = ..., (try { foo(resource) } finally { release(resource) })

without changing the completion value of the expression.

Good catch! (no pun intended)

I'll add this to

http://wiki.ecmascript.org/doku.php?id=harmony:completion_reform

Thanks,

# Brendan Eich (14 years ago)

On Nov 11, 2011, at 9:49 AM, Mark S. Miller wrote:

On Fri, Nov 11, 2011 at 9:13 AM, David Herman <dherman at mozilla.com> wrote: [...] Hence my do-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:do_expressions

or Brendan's subtly-disambiguated-block-statement-expressions:

http://wiki.ecmascript.org/doku.php?id=strawman:block_vs_object_literal

If Brendan's idea can be made to work, and it's not too confusing, I'm pretty sure I'd prefer it over do-expressions. [...]

Interesting. But I think I prefer do expressions over making JS parsing that much more subtle. JS is already much to hard to parse, and other accepted changes are already making it even more so.

Totally agree. I said at the last TC39 meeting that I've given up

# Brendan Eich (14 years ago)

On Nov 11, 2011, at 10:22 AM, gaz Heyes wrote:

On 11 November 2011 17:13, David Herman <dherman at mozilla.com> wrote: Your idea of mandatory parens is still valid (if, IMO, a bit unsatisfyingly verbose) for most statement forms. It's only the block-statement-expression that doesn't work. Hence my do-expressions

It should also apply to function expressions IMO too as there are instances especially in for loops where there is confusion between the two.

We are not going to make parens around function expressions mandatory -- gaz, is that what you meant?

Mark's idea need not mandate parens. We can make all statements that start with keywords be AssignmentExpression right parts, and extend the lookahead restriction under ExpressionStatement to forbid all those keywords. Then you'd have to parenthesize most of the time, but not in actual parameter lists, initialiser lists, or comma expressions.

The other problem, the lack of ASI one, is an incompatibility, but it may be tolerable:

let x = switch (y) {case 1:...default:...} (why_am_i_overparenthesized ? no_idea : maybe_because)(42);

This can't work in old browsers, and in new ones it would not trigger ASI, rather chained calls of whatever callee the switch evaluates to.

Requiring parens around the switch in an example like this may or may not help. Yes, today

let x = (parenthesized_for_some_reason()) (why_am_i_overparenthesized ? no_idea : maybe_because)(42);

is a known lack-of-ASI hazard in the language (imagine many newlines and even comments between the two lines). We can fix it only by making newlines more significant, note well.

But if we allow keyword-statements to be assignment expressions, parens are no guarantee that someone used ; where they should have.

# Brendan Eich (14 years ago)

On Nov 11, 2011, at 10:25 AM, gaz Heyes wrote:

On 11 November 2011 18:01, Dmitry Soshnikov <dmitry.soshnikov at gmail.com> wrote: var foo = { // do stuff 100; };

What would be the result of a labelled statement? You'd need labels to work within expressions since you'd probably want to do: x=loop:for(i=0;i<10;i++){ }

but then what if you do: x=1/loop:for(i=0;i<10;i++){ }

Is 1 divided by the result of the for loop or is it divided by undefined?

My last reply suggested making unlabeled keyword-statement (statements that start with a reserved identifier) be assignment expressions, so if you remove the loop: label, you would have to parenthesize:

x=1/(for(i=0;i<10;i++){ })

to get it to compile. If we extend things to support LabelledStatement as well as KeywordStatement as right parts of AssignmentExpression, then you'd still need parens and it would all work.

I haven't thought through other potential issues with labels, though.

As noted, blocks in parentheses are out. No can do.