Iterators, generators, finally, and scarce resources

# Andy Wingo (11 years ago)

On Tue 29 Apr 2014 20:35, David Herman <dherman at mozilla.com> writes:

it's a bad smell if code that creates an iterator in a for-of loop head, loops over it, and never even touches the iterator directly doesn't shut down the iterator.

There are no other objects in JS that have a finalization facility or other "shut down" semantics. And yet many of them can indeed hold onto scarce resources (to the extent that is possible in JS, which is to say, approximately never in the browser), and none of them have similar facilities.

This truly seems a case of the tail wagging the dog.

  • Iterators are intended to be short-lived (in particular, long-lived iterators over mutable data sources are invalidation hazards). So the common consumer of iterators, for-of, should properly dispose of them.

This is only one of the consumers of iterators. Should exceptions thrown while destructuring iterators also imply "shutdown"? I also note this is the first JS construct that would introduce the notion of resource acquisition and also of implicit finallies.

  • The uncommon case of using an iterator partially and in several loops can be easily implemented with combinators (or while loops), but we should be safe by default.

Again, it's only the holders of scarce resources that even need to consider this question of "safety", as it is only in their case that the question arises! Normal code -- the common case -- cannot be "unsafe".

  • The problems of "on cleanup" logic will be greatly exacerbated with future asynchronous iteration features, which are IMO an extremely high priority for the future. The overwhelming majority of JS programs that operate on sequences of data are doing so asynchronously. The moment we start going down the road of designing asynchronous iteration features (such as for (await ... of ...)), which in fact Jafar and I have been starting work on, the try/finally hazards will show up much more often. If we don't do proper disposal of synchronous iterators, we'll create an asymmetry between synchronous and asynchronous iteration, which would not only be a nasty wart but also a refactoring hazard.

Can you elaborate? This is getting quite far afield of the existing drafts :(

Incidentally I think that if TC39 decided to re-add this method, it should be called close() instead, because it doesn't make sense to "return" from a non-generator iterator.

I thought so at first too, until I remembered that iterators have a return value. So I still think return is the right name.

next() and throw() also have return values, FWIW...

However in this case it is possible to arrange to close the iterator, with a different interface:

This is a dramatic weakening of the power of iterators, in that you force all iteration abstractions to expose their external resources to consumers. Again, it may not seem like a big deal now but it'll be completely unacceptable for the asynchronous case.

Again this argument needs to provide more details on the asynchronous case, and as the solution I propose, I think it makes sense -- it's the same code that has the responsibility for cleaning up either way, whether it's implicit, opaque, and not configurable (Jafar's proposal) or explicit and configurable (status quo).

== return() in generators is semantically weird

It's not as weird as you make it out to be. Return is not much different from throw, first of all. Note also that ES7 do-expressions allow expressions to return.

ES7 do-expressions mark their returns with "return" or otherwise via tail position AFAIU. My argument was that making "yield" have three continuations tips the balance for me into bogosity.

Anyway this is going back and forth on nits; I'll just pick a couple more below. I've probably repeated myself enough over the years!

== Calling return() on early exit from for-of is expensive

Wrapping a try/finally around each for-of is going to be really expensive in all engines right now. I'm skeptical about our ability to optimize this one away. Avoiding try/catch around for-of was one reason to move away from StopIteration, and it would be a pity to re-impose this cost on every for-of because of what is, in the end, an uncommon use case.

This glosses over a critical difference: the StopIteration semantics required catching exceptions on every iteration of the loop. This semantics only requires a check on loop exit.

This is true. And as Filip notes, this isn't a fundamental perf problem -- it can be worked around. But it's also true (AFAIK) that no engine optimizes try/finally right now.

I think the expected result of doing this would be performance lore to recommend using other iteration syntaxen instead of for-of.

This argument seems fishy to me. There is no comparable syntax in JS for for-of and generators, so I think the alternative would be stuff like higher-order methods (a la .forEach). I find it hard to believe that a single check on the outside of the loop will make or break their ability to compete with higher-order methods.

It's a question of whether the loop as a whole is ion'd, dfg'd, ftl'd, crankshaft'd, etc or not.

,

Andy

# Kevin Smith (11 years ago)

I also note this is the first JS construct that would introduce the notion of resource acquisition and also of implicit finallies.

And that is why late changes like this are risky. As far as I know, there's been no published work exploring how this aspect of for-of would interact with future designs which might address resource management in general.

Furthermore, as far as I know we have absolutely no real-world experience with how such an auto-cleanup construct would actually fare in javascript. There is no time to do the homework that this effort would properly require.

# Boris Zbarsky (11 years ago)

On 4/30/14, 3:09 AM, Andy Wingo wrote:

There are no other objects in JS that have a finalization facility or other "shut down" semantics.

This has actually come up as a problem for DOM objects. Some people actually want a semi-generic facility to say "I'm done with you" instead of having to have per-object-type hacks...

And yet many of them can indeed hold onto scarce resources (to the extent that is possible in JS, which is to say, approximately never in the browser)

It happens all the time in the browser. You can hold on to address space (e.g. via huge typed arrays). You can hold on to graphics card resources via WebGL objects. I expect to see more instances of this as we expose more hardware capabilities to the web.

and none of them have similar facilities.

This is certainly true, sadly.