Skip to content

Instantly share code, notes, and snippets.

@dherman

dherman/ltr.md Secret

Last active October 13, 2015 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dherman/b250d1fad15dbb5f77a5 to your computer and use it in GitHub Desktop.
Save dherman/b250d1fad15dbb5f77a5 to your computer and use it in GitHub Desktop.
Left-to-right comprehensions

Proposal: left-to-right comprehensions

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.

Realistic examples

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

Anti-proposals

Allow me to anticipate some alternatives that might come up, to head them off:

Paren-free left-to-right

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

Explicit yield for generator expressions

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.

Aternative bracketing like [* ... *] for generator expressions

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#.

Using { ... } for generator expressions

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.

Grammar

ArrayLiteral           ::= ... | "[" Comprehension "]"
PrimaryExpression      ::= ... | "(" Comprehension ")"
Comprehension          ::= ForComprehensionClause ComprehensionClause* Expression
ComprehensionClause    ::= ForComprehensionClause
                        |  IfComprehensionClause
ForComprehensionClause ::= "for" "(" LHSExpression "of" Expression ")"
IfComprehensionClause  ::= "if" "(" Expression ")"

Removed

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment