Nannying (was: Array comprehension syntax)

# Jason Orendorff (13 years ago)

I want to defend my kind of nannying. :-)

I favor rejecting if x = y in comprehensions because it's almost always going to be a bug, and a tough bug to diagnose. Mistaken assumptions tend to stay invisible for a long time. The more fundamental the mistake, the harder it is to see.

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

The behavior here is really astonishing. It's hard to see how the developer is supposed to figure out what's wrong. I'm willing to take on some implementation complexity to head off this kind of frustration.

Beyond likely mistakes, my sympathy for nannying drops off quickly. In particular, there's little to gain from trying to enforce readability in JS at the language level. It's just not that kind of language.

# Allen Wirfs-Brock (13 years ago)

On Sep 24, 2012, at 10:37 AM, Jason Orendorff wrote:

I want to defend my kind of nannying. :-)

I favor rejecting if x = y in comprehensions because it's almost always going to be a bug, and a tough bug to diagnose. Mistaken assumptions tend to stay invisible for a long time. The more fundamental the mistake, the harder it is to see.

The argument could validly be made for IfStatement which we cannot change, for compatibility reasons. To me, if is probably mostly an issue of whether we want consistency between IfStatement and comprehension if clauses. consistency is good but we've already discussed that we probably don't want to have Expression for if clauses because of comma confusion. I think this is a discussion that could go either way, it's just good to consider the full range of principles we are factoring into the decision

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

The behavior here is really astonishing. It's hard to see how the developer is supposed to figure out what's wrong. I'm willing to take on some implementation complexity to head off this kind of frustration.

I agree that this is a real hazard. I think eliminating it requires two token look-ahead. If that is ok, then we could specify

ConciseBody : [lookahead ∉ { { }] AssignmentExpression {[lookahead ∉ { IdentifierName }] [lookahead ∉ { :}] FunctionBody }

# Rick Waldron (13 years ago)

On Mon, Sep 24, 2012 at 1:37 PM, Jason Orendorff <jason.orendorff at gmail.com>wrote:

snip

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

Per the proposal, this is already illegal, otherwise requiring parens around the object literal. The example given in the proposal is:

// Parenthesize the body to return an object literal expression let key_maker = val => ({key: val});

# Erik Arvidsson (13 years ago)

On Mon, Sep 24, 2012 at 3:20 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Mon, Sep 24, 2012 at 1:37 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

snip

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

Per the proposal, this is already illegal, otherwise requiring parens around the object literal. The example given in the proposal is:

It is a valid block that starts with a label :'(

# Kevin Smith (13 years ago)

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

The behavior here is really astonishing. It's hard to see how the developer is supposed to figure out what's wrong. I'm willing to take on some implementation complexity to head off this kind of frustration.

I agree that this is a real hazard. I think eliminating it requires two token look-ahead. If that is ok, then we could specify

*ConciseBody *:

[lookahead ∉ { { }]* AssignmentExpression **{*[lookahead ∉ { IdentifierName }] [lookahead ∉ { :}] *FunctionBody *}

The hazard would still be there in other (admittedly less frequent) forms, though:

var books = isbns.map(isbn => { isbn });

But this is the kind of thing that linters are really good at (pointing out constructs that might be errors).

# Rick Waldron (13 years ago)

On Mon, Sep 24, 2012 at 3:24 PM, Erik Arvidsson <erik.arvidsson at gmail.com>wrote:

On Mon, Sep 24, 2012 at 3:20 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Mon, Sep 24, 2012 at 1:37 PM, Jason Orendorff < jason.orendorff at gmail.com> wrote:

snip

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

Per the proposal, this is already illegal, otherwise requiring parens around the object literal. The example given in the proposal is:

It is a valid block that starts with a label :'(

It sure is! Poor choice of words using "illegal", what I meant to say was that it wouldn't return an object literal as the user might expect, unless it has the parens.

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

I agree that this is a real hazard. I think eliminating it requires two token look-ahead. If that is ok, then we could specify

/ConciseBody /:

[lookahead ∉ { {}]/AssignmentExpression /{[lookahead ∉ { /IdentifierName/}] [lookahead ∉ { :}] /FunctionBody /}

See doku.php?id=strawman:arrow_function_syntax&rev=1307152413 and later revisions at

doku.php?do=revisions&id=strawman%3Aarrow_function_syntax

These lookahead(2) restrictions I drafted in the strawman stage of arrow function syntax are not enough if we keep extending object literals. But perhaps we should stop extending object literals.

# Brendan Eich (13 years ago)

Also, in case anyone missed it:

strawman:block_vs_object_literal

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

The argument could validly be made for IfStatement which we cannot change, for compatibility reasons. To me, if is probably mostly an issue of whether we want consistency between IfStatement and comprehension if clauses. consistency is good but we've already discussed that we probably don't want to have Expression for if clauses because of comma confusion. I think this is a discussion that could go either way, it's just good to consider the full range of principles we are factoring into the decision

The one distinguishing factor in the comprehension case is the paren-free 'if' head. IfStatement has less of an issue with comma and assignment at top level of the condition, although assignment is a constant worry for all C-family languages that allow it. GNU C has a warning for this case, you avoid it when you intend to assign at top level of the condition by over-parenthesizing:

if ((yes = i_am_sure()) {...}

C++ has nice binding variants:

if (int yes = i_am_sure()) { /* yes in scope only here */ }

and this gets '=' back in the door, but as an declaration-initializing operator, not the assignment operator that could be confused or typo'ed from ==.

To loop back to your earlier point, in a comprehension:

a = [e for e in set if log(e), e & 1];

to log all elements in the set, but comprehend only the odd ones, looks a bit odd. Is that ',' separating parts of the if condition, or element initializers in an array literal?

But there's no ambiguity here, AFAICT. See the toy grammar below. Paren-free heads are still bracketed in comprehensions, by 'if'/'for'/'let'/']' in full. Note that this would not be the case of the comprehension expression moved to the right.

I don't believe the paren-free syntax increases the odds of typo'ing or otherwise mistakenly writing = for ==, so on reflection, I'm not too worried about comma and assignment expressions as paren-free if conditions in comprehensions. But I don't think it would be a big deal to forbid them either.

/be

%token FOR %token ID %token IF %token OF

%%

ArrLit: '[' AssExpList ']' | '[' AssExp FOR ID OF AssExp IF Exp ']' ;

AssExpList: AssExp | AssExpList ',' AssExp ;

Exp: AssExp | Exp ',' AssExp ;

AssExp: ID | ID '=' AssExp ;

# David Herman (13 years ago)

On Sep 24, 2012, at 12:37 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

I want to defend my kind of nannying. :-)

I favor rejecting if x = y in comprehensions because it's almost always going to be a bug, and a tough bug to diagnose.

I don't. Where do you draw the line? if (x = y) as well? if ((x = y))? This is enforcing idioms that JS does not enforce elsewhere. Leave this to the linters.

Here's another program I think we should reject, for the same reason:

var titles = books.map(b => {title: b.title});

Again, I disagree and think this should be left to linters. I don't want to start partially enforcing coding/parenthesis styles in a limited set of contexts in the language.

Beyond likely mistakes, my sympathy for nannying drops off quickly. In particular, there's little to gain from trying to enforce readability in JS at the language level. It's just not that kind of language.

Here we agree. :)

# Allen Wirfs-Brock (13 years ago)

On Sep 24, 2012, at 9:00 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

The argument could validly be made for IfStatement which we cannot change, for compatibility reasons. To me, if is probably mostly an issue of whether we want consistency between IfStatement and comprehension if clauses. consistency is good but we've already discussed that we probably don't want to have Expression for if clauses because of comma confusion. I think this is a discussion that could go either way, it's just good to consider the full range of principles we are factoring into the decision

The one distinguishing factor in the comprehension case is the paren-free 'if' head. IfStatement has less of an issue with comma and assignment at top level of the condition, although assignment is a constant worry for all C-family languages that allow it. GNU C has a warning for this case, you avoid it when you intend to assign at top level of the condition by over-parenthesizing:

if ((yes = i_am_sure()) {...}

C++ has nice binding variants:

if (int yes = i_am_sure()) { /* yes in scope only here */ }

and this gets '=' back in the door, but as an declaration-initializing operator, not the assignment operator that could be confused or typo'ed from ==.

To loop back to your earlier point, in a comprehension:

a = [e for e in set if log(e), e & 1];

to log all elements in the set, but comprehend only the odd ones, looks a bit odd. Is that ',' separating parts of the if condition, or element initializers in an array literal?

But there's no ambiguity here, AFAICT. See the toy grammar below. Paren-free heads are still bracketed in comprehensions, by 'if'/'for'/'let'/']' in full. Note that this would not be the case of the comprehension expression moved to the right.

I don't believe the paren-free syntax increases the odds of typo'ing or otherwise mistakenly writing = for ==, so on reflection, I'm not too worried about comma and assignment expressions as paren-free if conditions in comprehensions. But I don't think it would be a big deal to forbid them either.

I'm not particularly concerned about the = typo issue. As you say, it's nothing new for C-style languages. But I do think that unparenthesized commas in comprehensions would impact the readability of code because of the syntax sharing for object literals.

I note that in your toy grammar you avoid the comma issue at the front and in the for clauses by using AssignmentExpression in those positions.

The current ES6 spec. draft has AssignmentExpression at the front, and Expression in the for and if clauses. However, there is also a editor's note that suggests that AssignmentExpression should be considered for all three positions.

I think we should go the all AssignmentExpression route. It avoid ArrayLiteral-like comma confusion in all three positions. It is also a simpler set of rules to teach and remember. Finally, it leave open the future possibility of ArrayLiterals that have comprehensions in element positions. (not that I'm recommending this...)

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

I note that in your toy grammar you avoid the comma issue at the front and in the for clauses by using AssignmentExpression in those positions.

The current ES6 spec. draft has AssignmentExpression at the front, and Expression in the for and if clauses. However, there is also a editor's note that suggests that AssignmentExpression should be considered for all three positions.

There's still no ambiguity if the toy grammar changes to

%token FOR %token ID %token IF %token OF

%%

ArrLit: '[' AssExpList ']' | '[' AssExp FOR ID OF Exp IF Exp ']' ;

AssExpList: AssExp | AssExpList ',' AssExp ;

Exp: AssExp | Exp ',' AssExp ;

AssExp: ID | ID '=' AssExp ;

The "readability" concern would matter if people actually wrote unparenthesized comma expressions in such comprehensions, but Nanny-me says they won't, and Mary Poppins agrees.

Again, it's not our job to prevent people from writing something that might confuse someone. JS has lots of room in its grammar for that. I agree with Dave, in absence of evidence of real ambiguity or another technical grammar problem.

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

I think we should go the all AssignmentExpression route. It avoid ArrayLiteral-like comma confusion in all three positions. It is also a simpler set of rules to teach and remember.

It's different from the statement forms, though.

Again, this is a non-issue in practice. Nobody writes comma expressions in those places.

Finally, it leave open the future possibility of ArrayLiterals that have comprehensions in element positions. (not that I'm recommending this...)

Why wouldn't comprehensions be allowed in ArrayLiteral element positions? That falls out of any grammar that puts comprehensions at PrimaryExpression precedence, where they must be.

This is not a huge deal and I defer to Jason as champion-apparent, but it does seem worth agreeing on the same Expression non-terminal for if and for conditions in both statements and comprehensions, or finding a better reason to change in the latter.

# Allen Wirfs-Brock (13 years ago)

On Sep 25, 2012, at 1:30 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I think we should go the all AssignmentExpression route. It avoid ArrayLiteral-like comma confusion in all three positions. It is also a simpler set of rules to teach and remember.

It's different from the statement forms, though.

The statement form of if is already different in that parens are required. If you have the AssignmentExpression restriction on the paren-feree comprehension if clause a parenthesized comma expression would still be valid and look exactly like the statement form.

Again, this is a non-issue in practice. Nobody writes comma expressions in those places.

Finally, it leave open the future possibility of ArrayLiterals that have comprehensions in element positions. (not that I'm recommending this...)

Why wouldn't comprehensions be allowed in ArrayLiteral element positions? That falls out of any grammar that puts comprehensions at PrimaryExpression precedence, where they must be.

Just to be clear about what I meant... I can imagine in the future somebody proposing an extension to ArrayLiteral that would permit something like: let a = [0, n for n in range(1,10) if !(n%2), 100]; which produced similar array to [0,1,3,5,7,9,2,4,6,8,10,100].

If ES6 permits Expression in if clauses this would be incompatible change.

Like you say, it's not a big deal and not necessarily an extension that I would be a fan of.

# Allen Wirfs-Brock (13 years ago)

On Sep 25, 2012, at 1:27 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I note that in your toy grammar you avoid the comma issue at the front and in the for clauses by using AssignmentExpression in those positions.

The current ES6 spec. draft has AssignmentExpression at the front, and Expression in the for and if clauses. However, there is also a editor's note that suggests that AssignmentExpression should be considered for all three positions.

There's still no ambiguity if the toy grammar changes to

agreed, this isn't about formal parsing ambiguity.

...;

The "readability" concern would matter if people actually wrote unparenthesized comma expressions in such comprehensions, but Nanny-me says they won't, and Mary Poppins agrees.

Again, it's not our job to prevent people from writing something that might confuse someone. JS has lots of room in its grammar for that. I agree with Dave, in absence of evidence of real ambiguity or another technical grammar problem.

So, from that perspective, why didn't you make it:

ArrLit: '[' AssExpList ']' | '[' Exp FOR ID OF Exp IF Exp ']'

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

So, from that perspective, why didn't you make it:

ArrLit: '[' AssExpList ']' | '[' Exp FOR ID OF Exp IF Exp ']'

Because that is ambiguous, with a reduce/reduce conflict on lookahead ',':

state 6

 3 AssExpList: AssExp .
 5 Exp: AssExp .

 FOR       reduce using rule 5 (Exp)
 ','       reduce using rule 3 (AssExpList)
 ','       [reduce using rule 5 (Exp)]
 $default  reduce using rule 3 (AssExpList)

You can spot it by inspection given

AssExpList: AssExp | AssExpList ',' AssExp ;

and

Exp: AssExp | Exp ',' AssExp ;

# Andreas Rossberg (13 years ago)

On 26 September 2012 00:23, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Sep 25, 2012, at 1:30 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I think we should go the all AssignmentExpression route. It avoid ArrayLiteral-like comma confusion in all three positions. It is also a simpler set of rules to teach and remember.

I'm with Allen. We should not have unbracketed commas in an array literal for anything else but separating elements. For both consistency and future proofness.

# Brendan Eich (13 years ago)

Andreas Rossberg wrote:

On 26 September 2012 00:23, Allen Wirfs-Brock<allen at wirfs-brock.com>
wrote:

On Sep 25, 2012, at 1:30 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

I think we should go the all AssignmentExpression route. It avoid ArrayLiteral-like comma confusion in all three positions. It is also a simpler set of rules to teach and remember.

I'm with Allen. We should not have unbracketed commas in an array literal for anything else but separating elements. For both consistency and future proofness.

Let's agree that AssignmentExpression is ok (required for the left-most comprehension expression). The main nannying objection was to trying to ban = at top level of if and for heads.

If we can settle on this, I'm ok with it given the custom grammar already required for if and for in comprehensions. It's not like anyone will truly want / need to write comma expressions in heads, as I said yesterday!

Separate question to you: (for|let|if)+ is what Jason championed, are you on board?

# Andreas Rossberg (13 years ago)

On 26 September 2012 15:02, Brendan Eich <brendan at mozilla.com> wrote:

Let's agree that AssignmentExpression is ok (required for the left-most comprehension expression). The main nannying objection was to trying to ban = at top level of if and for heads.

If we can settle on this, I'm ok with it given the custom grammar already required for if and for in comprehensions. It's not like anyone will truly want / need to write comma expressions in heads, as I said yesterday!

Sounds good to me.

Separate question to you: (for|let|if)+ is what Jason championed, are you on board?

Right, I see no reason to artificially restrict the syntax to specific cases, especially given that it wouldn't make the expansion any simpler. So I also favour (for|if)+ or (for|let|if)+ (having let makes sense, although it probably isn't super important).

# Jason Orendorff (13 years ago)

On Wed, Sep 26, 2012 at 8:22 AM, Andreas Rossberg <rossberg at google.com> wrote:

Separate question to you: (for|let|if)+ is what Jason championed, are you on board?

Right, I see no reason to artificially restrict the syntax to specific cases, especially given that it wouldn't make the expansion any simpler. So I also favour (for|if)+ or (for|let|if)+ (having let makes sense, although it probably isn't super important).

It isn't really. The sort of code where you want it is like:

[x for e of elements
       let x = get_some_data_out_of(e)
           if x]

It doesn't come up very often, but when it does, if you can't use let, the workarounds are things like:

// use "for-of" to mean "let"
[x for e of elements
       for x of [get_some_data_out_of(e)]   // ಠ_ಠ
           if x]

// go ahead and repeat yourself
[get_some_data_out_of(e) for e of elements
                             if get_some_data_out_of(e)]  // ಠ益ಠ

// declare a temp variable, assign in the if-condition
var x;
[x for e of elements
       if x = get_some_data_out_of(e)]   // (ಥ﹏ಥ)

// use "for-of" to mean "let" in a different way
[x for x of [get_some_data_out_of(e) for e of elements]
       if x]  // ⊙﹏⊙

The main drawback of comprehensions is the temptation to get "clever". It might seem that providing 'let' in comprehensions would exacerbate that, but on balance I think it actually helps.

# Andreas Rossberg (13 years ago)

On 26 September 2012 16:02, Jason Orendorff <jason.orendorff at gmail.com> wrote:

The main drawback of comprehensions is the temptation to get "clever". It might seem that providing 'let' in comprehensions would exacerbate that, but on balance I think it actually helps.

I agree on both accounts, and AFAICT that also seems to match the experience in Haskell.

# Allen Wirfs-Brock (13 years ago)

On Sep 26, 2012, at 6:22 AM, Andreas Rossberg wrote:

On 26 September 2012 15:02, Brendan Eich <brendan at mozilla.com> wrote:

Let's agree that AssignmentExpression is ok (required for the left-most comprehension expression). The main nannying objection was to trying to ban = at top level of if and for heads.

If we can settle on this, I'm ok with it given the custom grammar already required for if and for in comprehensions. It's not like anyone will truly want / need to write comma expressions in heads, as I said yesterday!

Sounds good to me.

this is also what I favor, AssignmentExpression all around

Separate question to you: (for|let|if)+ is what Jason championed, are you on board?

Right, I see no reason to artificially restrict the syntax to specific cases, especially given that it wouldn't make the expansion any simpler. So I also favour (for|if)+ or (for|let|if)+ (having let makes sense, although it probably isn't super important).

To me, adding let in combination with multiple ifs feels like a significant extension to what was in the original proposal that was accepted for ES6. It seems like the sort of proposal change that should be presented at a TC39 meeting.

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

To me, adding let in combination with multiple ifs feels like a significant extension to what was in the original proposal that was accepted for ES6. It seems like the sort of proposal change that should be presented at a TC39 meeting.

Jason did propose (for|let|if)+, see

proposals:iterators_and_generators#comprehensions

This was missed when Dave carried things over to the new wiki namespaces. It has been discussed at TC39 meetings.

Asserting significance won't do. Andreas and I have agreed with Jason that the desugaring is straightforward in all cases and you haven't responded directly to that argument. We have lots of detailing to do for ES6, and this is not out of line with other missed or overlooked problems in proposals.