Moving the body of array comprehensions and generator expressions to their right-hand side would be, I claim, a significant readability improvement. A simple example: instead of
[x * y for x of a for y of b]
(x * y for x of a for y of b)
you would instead write
[for (x of a) for (y of b) x * y]
(for (x of a) for (y of b) x * y)
The big win here is with multiple clauses. The Pythonic syntax that's in the current wiki is confusing because it leaves you wondering which is the outer loop and which is the inner loop, or whether they're nested at all. By contrast, this proposal follows the left-to-right, top-to-bottom reading order of normal nested JS loops. It's just immediately obvious that they nest, and in what order they nest.
Note that while Haskell and Python use the right-to-left syntax, F# and C# both have left-to-right syntax.
Here's Brendan's implementation of Norvig's Sudoku solver, implemented in both the right-to-left syntax and the left-to-right syntax. Some highlights:
Line 46, right to left:
return [a+b for (a of A) for (b of B)]
Line 46, left to right:
return [for (a of A) for (b of B) a+b]
Line 97, right to left:
if (all(eliminate(values, s, d2) for (d2 in values[s]) if (d2 != d)))
Line 97, left to right:
if (all(for (d2 of values[3]) if (d2 != d) eliminate(values, s, d2)))
Line 155, right to left:
return some(search(assign(values.copy(), s, d)) for (d in values[s]))
Line 156, left to right:
return some(for (d of values[s]) search(assign(values.copy(), s, d)))
Allow me to anticipate some alternatives that might come up, to head them off:
Causes parsing trouble with juxtaposed expressions. But I favor the parens anyway because I believe the paren-free syntax is too hard to visually parse. (Unlike Brendan's original paren-free statement forms, in comprehensions it lacks any punctuation to separate the different clauses.)
This looks too much like it's yielding to the outer function, since generator expressions don't have an explicit function*()
or *()
in their syntax. And they shouldn't! Generator expressions are a syntactic abstraction for creating generator objects, not for creating generator functions. That they could be implemented by generator functions is an implementation detail that shouldn't be leaked.
Playing with bracketing syntax goes off the rails, opening up too much to blue-sky syntactic exploration. I'm making a narrow-scoped proposal here, and staying within the scope of existing practice. There's precedent for (...)
in Python and left-to-right in F#/C#.
Pretty-looking, but again I'm not interested in syntactic innovation here. And anyway it requires several tokens of lookahead since { for() { } }
is a valid object literal, which is fragile in the face of potential future extensions to object literal syntax.
ArrayLiteral ::= ... | "[" Comprehension "]"
PrimaryExpression ::= ... | "(" Comprehension ")"
Comprehension ::= ForComprehensionClause ComprehensionClause* Expression
ComprehensionClause ::= ForComprehensionClause
| IfComprehensionClause
ForComprehensionClause ::= "for" "(" LHSExpression "of" Expression ")"
IfComprehensionClause ::= "if" "(" Expression ")"
I eliminated the special case exception for the Arguments
non-terminal that allows generator expressions to remain unparenthesized, because this is controversial and arguably bad style anyway.
I also eliminated let
clauses because it's unclear what the best syntax should be: parenthesized let
clauses don't exist in ES6, and this approach doesn't accommodate paren-free. Besides, SpiderMonkey has had comprehensions without let
for a while and hasn't had any complaints. So adding let
clauses would be deferred for reconsideration post-ES6.