(Almost) everything is expression
On 11.11.2011 11:07, Dmitry Soshnikov wrote:
P.S:
Regarding Dave's
do { .. }
-- we may omitdo
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.
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
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.
<aside note>
let x = q ? 10 : 20;
Why we're reinventing the wheel here is up to me.
</aside>
-----Message d'origine---
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.
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 :
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---
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() }
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.
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.
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.
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
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---
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:
- Switch-expression (as addition, I eliminated
break
)let a = switch (foo) { case 10: 100; default: 200; };
- 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
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.
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).
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.
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(;;).
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.
Insist on enclosing parens, since "(" introductory-token is not otherwise legal
let a = (switch (foo) { case 10: 100; default: 200; });
- If-expression:
let a = (if (foo) { print('a is foo'); foo; } else { // do some longer stuff });
- 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.
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; };
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.
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.
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 omitdo
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
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.
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.
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.
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
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.
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"
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 omitdo
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
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
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.
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.
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?
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:
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
eval
s -- 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.
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.
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.
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.
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!"
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.
I would translate "How X is that?" as "that is very X!" :)
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.
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,
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
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.
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.
(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:
break
)let a = switch (foo) { case 10: 100; default: 200; };
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.
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).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 omitdo
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.