Catch-all proposal based on proxies

Mike Samuel mikesamuel at gmail.com
Thu Dec 10 12:54:36 PST 2009


2009/12/10 Brendan Eich <brendan at mozilla.com>:
> On Dec 10, 2009, at 11:31 AM, Mike Samuel wrote:
>
>> I was assuming iterators would work without clients explicitly
>> trapping STOP_ITERATION, which is what proxies would provide.   But I
>> suppose if we're doing proxies, we can also do the syntactic sugar to
>> do away with that in new looping constructs.
>
> We do indeed have for-in loops, comprehensions, and generator expressions
> all automatically catching StopIteration in SpiderMonkey and Rhino. There is
> hardly ever a reason to write a try/catch -- just as in Python.
>
>
>> Proxy based iterators work well with existing loop constructs though
>>   while ('next' in iterator) doSomething(iterator.next);
>
> There are lots of convenient ways to express iteration, but one that
> mandates proxies is inherently heavier and harder to optimize than I think
> we (implementors or in some cases users) want.
>
>
>> This works because I used a different definition of iterator.  In my
>> example, the producer has almost exactly the same contract as the next
>> method of your iterator, differing only in the value thrown, and the
>> proxy served to convert it to an iterator.
>> The iterator then an object such that
>>  - there is no next property iff the iterator is exhausted !('next'
>> in iterator)
>>  - the next value in the series can be retrieved by reading the next
>> property which advances the iterator
>>  - optionally, setting or deleting the next property mutates the
>> last element returned on the underlying collection
>
> The last element, or the next element?

Previous as in java Iterators.


> Proxies can propagate effects to other objects, for sure, but this is not
> only expensive, it's also hard to analyze. An iteration protocol should be
> more functional (pure).



> We chose to borrow from Python first in the spirit of programming language
> design by borrowing from older languages, second to reuse developer
> brainprint.

Fair enough.


> But the functional (with light OO dusting on top, which could be removed as
> noted -- getting rid of |this|) flavor of the iteration protocol in
> SpiderMonkey and Rhino JS1.7+ is winning both for implementors and users, in
> our experience.



> To criticize our JS1.7/1.8 experience a bit:
>
> A. The obvious problem is that we hang the meta-level handler off the
> base-level object, as Python does with its __iter__. But
> Object.defineIterator seems like the fix for this bug.
>
> B. Creating an object with a next method instead of a closure may be one
> object too many. The object need not carry any state that's not in the next
> method's environment (as in the examples I showed).

> But the object does provide an identity separate from next, in which send,
> throw, and close methods are bound for generators. And the object allows
> state representation optimizations other than closure-based ones.

It would extend to set/delete as in java though.
Java has basically ignored set/delete on iterators in the syntactic
sugar around Iterables.


> C. Generators, being flat (one frame of activation saved) yet heap-escaping,
> don't always compose nicely. This has spawned PEP 380
> (http://www.python.org/dev/peps/pep-0380/).

Generators compose in some ways though the syntax can be awkward:
def zip(a, b):
  for a_el in a:
    yield (a_el, b.next())
  try:
    b.next()
  except StopIteration:
    pass
  except:
    raise AssertionError('%r not exhausted', b)

The part that does the work is straight-forward, and then the corner
case checking is, as usual, the most verbose.


> Generators are nevertheless quite convenient according to reports from our
> users, and pretty easy to implement in any implementation that compiles into
> bytecode or something more sophisticated, instead of walking ASTs to
> evaluate code.

Cool.


> It's important not to oversell generators as solving concurrency issues or
> being "coroutines" -- they are best thought of as the simplest way to write
> an iteration protocol handler (or more casually, "an iterator"). Here are
> the example iterators recast as generators:
>
> function keyIterator() {
>    let keys = Object.keys(this);
>    for (let i = 0; i < keys.length; i++)
>        yield keys[i];
> }
>
> function indexIterator() {
>    for (let i = 0; i < this.length; i++)
>        yield i;
> }
>
> When called, a generator returns an iterator, so these are factories. The
> functional version had to return objects with next methods:
>
> function keyIterator() {
>    let keys = Object.keys(this);
>    let i = 0;
>    return {
>        next: function () {
>            if (i == keys.length)
>                throw StopIteration;
>            return keys[i++];
>        }
>    };
> }
>
> function indexIterator() {
>    let self = this;
>    let i = 0;
>    return {
>        next: function () {
>            if (i == self.length)
>                throw StopIteration;
>            return i++;
>        }
>    };
> }
>
> This use-case is where generators shine.

I think your arguments against using proxies to implement iterators
are strong, so maybe keys/enumerate should be considered tentative so
that they can be informed by any separate work on iterators.

Again, separating out the concept of own-ness from key enumeration
might make that easier.


> /be
>


More information about the es-discuss mailing list