jashkenas/coffee-script — Issue #2140: sugar for python-style iterators
Author: showell
Created: 2012-02-25 19:50:36+00:00
State: open

I am a big fan of Python's iterator protocol, which allows you to lazily create elements to iterate over in a loop. In CoffeeScript, it's easy to mimic Python's iterator protocol to create functions that produce elements one at a time, but there's no looping construct that directly supports this style of iteration.

To cut to the chase, I'd like to propose some sugar for this idiom:

```
while true
try
val = iterator()
catch e
break if e is "StopIteration"
throw e
# do stuff with val
# break/continue as needed
```

It would be nice if you could write this instead:

```
loop iterator, val
# do stuff with val
# break/continue as needed
```

The loop sugar would expand one line of code to six lines of code, so there's a pretty good bang for the buck. Here is the code that would get generated (which would in turn be translated to JS, of course):

```
while true
try
#{value} = #{iterator}()
catch e
break if e is "StopIteration"
throw e
```

The code doesn't introduce any new keywords, since "loop" already exists. I believe "loop" is used pretty infrequently, and I think the proposed syntax could be backward compatible.

Here's a longer example to provide context:

```
print_odd_squares_until_overflow = (iterator) ->
console.log "-- Squares:"
# it would be nice to sugar the next six lines
loop
try
val = iterator()
catch e
break if e is "StopIteration"
throw e
sq = val * val
continue if sq % 2 == 0
break if sq > 500
console.log sq
return

do ->
range_iterator = (low, high) ->
i = low - 1
->
i += 1
throw "StopIteration" if i > high
i

iterator = range_iterator(1, 25)
print_odd_squares_until_overflow iterator

do ->
natural_numbers = ->
# infinite iterator
i = 0
->
val = i
i += 1
val

iterator = natural_numbers()
print_odd_squares_until_overflow iterator
```

The example's a bit contrived, of course, but here are some key things to note:

1. The intention is for print_odd_squares_until_overflow() to be versatile. You don't have to pass it a list; you just need to pass it an iterator function.
1. It's not a simple matter of turning the loop/try/assign/catch/break/if/throw/do-stuff idiom into a function.

To elaborate on the last point, you can't trivially turn these lines of code into a callback:

```
sq = val * val
continue if sq % 2 == 0
break if sq > 500
console.log sq
```

The above snippet of code only works in the context of a block, because of the semantics of "break" and "continue". For generic iterators, "break" is particularly important, since often the reason to create iterators in the first place is to create an "infinite" stream of data that you eventually break out of processing.

Stepping back, I have several goals in filing this issue:

1. Ideally, the proposal gets accepted. :)
1. If not, it would be nice to have some discussion on better ways to achieve this style of coding without changing the language.
1. This might lead to some interesting tangential discussion on how Ruby and Python tackle this problem from totally different angles. Ruby nicely generalizes the concept of a block; Python nicely generalizes the concept of an iterator.
1. If the proposal simply gets rejected, it will be on record.

[Issue 983](https://github.com/jashkenas/coffee-script/issues/983) is vaguely related, but it's way more ambitious than what I'm asking for. I couldn't find any other related issues in a quick search.