On Jul 30, 2010, at 9:06 PM, felix wrote:

> it seems odd to me that if 'a' is an iterator, it will iterate over the 
> iterator's value stream instead of the iterator's properties, unless you 
> define the two to be identical, which would be strange.  eg, if you have an 
> input stream iterator f, would f.hasOwnProperty('bacon') work or not?

Sorry, I misspoke -- without having specified keys fully (we'll get to that 
task on the wiki), let me try to make up for it by implementing some variations 
on keys using proxies, in TraceMonkey.

The object returned by keys, called "a" in your revision, is a proxy. Let's 
call it "p" to avoid confusion.

Let's say that the handler for proxy "p" has no traps emulating properties, 
only an iterate trap. The iterate trap is a derived trap (it is optional; if 
missing, the default enumerate behavior is supplied by the proxy support in the 
runtime). The for-in loop calls the iterate trap on "p", which returns an 
iterator object.

This iterator, call it "it", is an object with a 'next' property whose value is 
a function taking no arguments and returning the next value in the iteration. 
This iterator object "it" is not necessarily a proxy -- it could be, but it 
need not be.

Ok, so the proxy denoted by "p" returned by keys(o) has been subject to for-in 
by being on the right of 'in', so its iterate trap has been called, and now the 
for-in runtime has the returned iterator "it".

The for-in loop runtime therefore calls it.next() implicitly at the top of each 
iteration of the loop body. If it.next() throws StopIteration, the for-in loop 
terminates normally (any other exception thrown by the body terminates the loop 
and is of course propagated).

Here's a keys implementation in bleeding edge SpiderMonkey 
(http://hg.mozilla.org/tracemonkey):

js> function keys(o) {
    const handler = {
        iterate: function () { for (let i in o) yield i; }
    };
    return Proxy.create(handler);
}
js> let o = {a:1, b:2, c:3};
js> for (let k in keys(o))
    print(k);
a
b
c
js> 

(The strawman at http://wiki.ecmascript.org/doku.php?id=strawman:iterators 
provides a convenience Iterator.create function to make a proxy given just the 
handler iterate trap function. Using it, we could simplify keys to just

function keys(o) {
    return Iterator.create(function () { for (let i in o) yield i; });
}

but the full expansion above, using Proxy.create, is not much longer.)

What if you wanted a proxy whose handler uses other traps in addition to 
iterate to emulate enumerable properties in full? That is doable too, thanks to 
the power of proxies:

js> function keys2(o) {
    const handler = {
        iterate: function () { for (let i in o) yield i; },
        getPropertyDescriptor: function (name) {
            if (o.hasOwnProperty(name))
                return Object.getOwnPropertyDescriptor(o, name); 
            return undefined;
        },
        getOwnPropertyDescriptor: function (name) {
            return Object.getOwnPropertyDescriptor(o, name); 
        },
        defineProperty: function (name, pd) {
            return Object.defineProperty(o, name, pd);
        },
        getOwnPropertyNames: function () {
            return Object.getOwnPropertyNames(o);
        },
        delete: function (name) {
            return delete o[name];
        },
        /*enumerate:*/
        fix: function () {
            return Object.freeze(o);
        }
    };
    return Proxy.create(handler);
}
js> let o = {a:1, b:2, c:3};
js> let p = keys2(o);
js> for (let k in p)
    print(k);
a
b
c
js> print(p.a);
1
js> print(p.b);
2
js> print(p.c);
3
js> delete p.a;
true
js> for (let k in p)
    print(k);
b
c

There's a workaround in this handler's getPropertyDescriptor trap, because we 
have not yet implemented Object.getPropertyDescriptor (it was ratified only 
this week at the TC39 meeting). I took the easy way out and instead of walking 
o's prototype chain, being careful to shadow, I handle only "own" properties 
(let's assume that Object.prototype has not been monkey-patched ;-).

This example shows how delete p.a followed by another for-in loop over p 
reveals that 'a' has been deleted. The proxy is implementing native object 
semantics. Of course it is possible to implement inconsistent object semantics 
with proxies -- this is true of host objects in general (especially prior to 
ES5, but even in ES5 -- see ES5 8.6.2, the paragraphs toward the end -- and of 
course implementations may defy the standard, or simply be buggy or old).

Proxies are thus an in-language facility for implementing host objects. This is 
an evolutionary path toward cleaning up host objects in various DOM 
implementations. TC39 thinks this is important; it may save us from the worst 
of the browser DOMs (the IE DOM can claim that title, but all browsers have 
quirky host objects in my experience).

I also implemented the iterate trap, which is lazy, in preference to 
implementing the commented-out enumerate trap, which is eager and used only to 
return property names, not arbitrary values. But since this example implements 
native object semantics, and since o has few properties, it would have been 
better to implement enumerate as well as iterate -- or instead of iterate if 
there's no reason to be lazy.

Why two traps, enumerate and iterate? Because a proxy can be a prototype of a 
non-proxy object, and for-in goes up the prototype chain. Having started with 
the enumerable properties of the non-proxy, going up the prototype chain to the 
proxy must not loop over arbitrary values, e.g. Fibonacci numbers. The for-in 
loop started enumerating property names, so it should continue doing so even 
if, somewhere up the prototype chain, it hits a proxy.

So, having started by enumerating a non-proxy, the for-in runtime finds a proxy 
up the prototype chain and calls the enumerate trap, not the iterate trap. This 
ends the prototype chain traversal, since proxies handle prototype delegation 
fully in the Harmony proxies design (this has been discussed here, see first 
cited text and reply at 
https://mail.mozilla.org/pipermail/es-discuss/2009-December/010313.html).

Implementing both iterate and enumerate allows a proxy to be iterated directly 
by for-in, but enumerated if on a prototype chain. The enumerate trap would 
return all the keys eagerly, in an array, that the iterate trap returns 
one-by-one via the 'next' method of the iterator it constructs.

So what you want can be implemented, but it need not be if only an iterator is 
needed. Besides the use-cases for iterators that entail lazy computation of a 
stream of values, the "large proxy" problem raised in the proxies proposal 
(near the bottom, look for "Waldemar") motivated the development of the iterate 
trap.

A large proxy is one emulating an object with a great many properties (possibly 
an unbounded number). A large proxy would have both iterate as well as 
enumerate in its handler, so if it were directly referenced on the right of 
'in' by a for-in loop, the large proxy's keys would not be eagerly stuffed in a 
large array via its enumerate trap -- instead the iterate trap would be called 
and provide an iterator returning the keys lazily.

(The large proxy's enumerate trap could indeed fail to complete, being subject 
to script runtime and memory quotas, if "large" really means "infinite"; such a 
large proxy should not be the prototype of non-proxy objects, clearly!)

But I expect, as is common (with necessary changes) in Python, that proxies 
with only an iterate trap will be sufficient for many useful iteration 
protocols. Such an iterable proxy could use no-ops for its fundamental traps, 
in order to appear to be an empty object. Or as in the first example, if only 
the iterate trap is supplied in the handler, the proxy is empty but attempts to 
get or test its properties (including 'toString') will throw.

We'll work out the remaining details, with help from es-discuss and even JS 
authors who experiment with Firefox 4 betas. I hope that this message helps by 
showing some of the options, as well as the rationale for extending proxies to 
support iteration in the way we've proposed.


> yes, I'd like generalized iterators like python, so this is not really an 
> argument.

What's not an argument?

I'm not arguing that we'd like iterators under just about any syntax, details 
to be decided. I'm arguing specifically, for several reasons, that we would be 
better off allowing for-in to be customizable as in Python:

1. Allows developers, not just TC39, to improve for-in semantics in parallel 
and at scale.

2. Avoids enlarging the language with another for-in-like construct.

3. Re-uses Python brainprint

I don't have a crystal ball, so I can't prove that these benefits override 
possible drawbacks. I'm encouraged by our experience with JS1.7 and up, since 
2006. Both SpiderMonkey and Rhino support iterators not as proxies, but with 
essentially the same semantics with respect to for-in. The Python precedent is 
also significant and relevant to many developers.

In all this time, no one has complained about for-in sometimes returning keys 
(or values for sequence types in Python, but keys for other types), other times 
returning custom iterator results. Note also that proxies as proposed for 
Harmony, and Python's unstratified meta-programming support, allow all sorts of 
fundamental opeations, not just for-in, to be customized.

But reason (1) is IMHO most compelling. Between my own mistakes made in haste 
in 1995 and TC39 TG1's failure in 1996 and 1997 with ES1 to clean up for-in and 
fully specify it, we have a legacy of confusion in a fundamental looping 
statement.

Meanwhile the mass of Ajax developers has coped by building some nice libraries 
on top of the language, including for-in, but of course expressing iteration 
functionally for want of a way to augment or reform the syntax.

>From this history I conclude that giving developers the ability to reform 
>for-in is likelier to bear good fruit than TC39 taking another rare and 
>probably one-time-only, hard-coded-in-the-spec, stab at the problem, using 
>novel yet less desirable syntax.

The Web is a very large evolving system. Giving developers evolutionary paths 
toward better ways of doing everyday tasks *in practice* works better than 
dictating "final answers" via de-jure standards bodies every few years or 
decades. As noted above, iteration may let authors reform for-in, even as the 
full power of proxies helps JS authors and DOM developers (certainly Mozilla's, 
and I believe also IE's) reform the DOM.

But we need these evolutionary pathways. They are necessarily meta-programming 
APIs, so JS developers can write programs determining the meaning of program 
elements including objects, properties, and for-in loops.

Without such meta-programming facilities, not much will change, and what does 
change will come in the form of highly constrained (over-constrained, in my 
experience), infrequently released, and costly products of design by committee.

/be
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to