[[Enumerate]] and enumerate and keys trap

# David Bruant (13 years ago)

In the latest spec draft, the [[Enumerate]] internal operation has an "includePrototype" argument (it also has an "onlyEnumerable" argument, but that's irrelevant to my point). for..in loops use [[Enumerate]] with includePrototype true and Object.keys/getOwnPropertyNames use [[Enumerate]] with includePrototype false. It looks a bit backwards to me. What about the following:

  1. [[Enumerate]] only deals with own properties (and doesn't need the "includePrototype" argument any longer). Arguably the operation can renamed "[[getOwnKeys]]" or the like
  2. for..in climbs the prototype chain itself and uses the own version of property names enumeration on each object of the prototype chain
  3. Object.keys/getOwnPropertyNames just use [[Enumerate]]

If the internal [[Enumerate]] only deals with own properties, it seems that the enumerate trap can be removed and for..in loop can use the keys and getPrototypeOf traps instead (as it does with regular objects which was my point 2).

Even if not adopting the changes I just suggested, it seems that we need some harmonization between the proxy traps and the internal [[Enumerate]] signatures.

# Tom Van Cutsem (13 years ago)

2012/10/8 David Bruant <bruant.d at gmail.com>

If the internal [[Enumerate]] only deals with own properties, it seems that the enumerate trap can be removed and for..in loop can use the keys and getPrototypeOf traps instead (as it does with regular objects which was my point 2).

It all sounds plausible. For proxies and for-in loops there's an important inversion-of-control though: currently the proxy takes control of a for-in loop (it decides whether to continue the loop with its prototype), with your proposal the proxy would lose that control. For all other traps that may be called during a prototype-chain-walk (get, set, has) the proxy always gets to "virtualize its prototype chain".

Even if not adopting the changes I just suggested, it seems that we need

some harmonization between the proxy traps and the internal [[Enumerate]] signatures.

Indeed. If we stick with the current [[Enumerate]](includePrototype, onlyEnumerable) signature we'll need an ugly preamble for proxies:

  • when includePrototype is false and onlyEnumerable is true, call the keys trap
  • when includePrototype is false and onlyEnumerable is false, call the getOwnPropertyNames trap
  • when includePrototype is true and onlyEnumerable is true, call the enumerate trap
  • any other combination should not occur
# Allen Wirfs-Brock (13 years ago)

On Oct 8, 2012, at 9:29 AM, Tom Van Cutsem wrote:

2012/10/8 David Bruant <bruant.d at gmail.com> If the internal [[Enumerate]] only deals with own properties, it seems that the enumerate trap can be removed and for..in loop can use the keys and getPrototypeOf traps instead (as it does with regular objects which was my point 2).

It all sounds plausible. For proxies and for-in loops there's an important inversion-of-control though: currently the proxy takes control of a for-in loop (it decides whether to continue the loop with its prototype), with your proposal the proxy would lose that control. For all other traps that may be called during a prototype-chain-walk (get, set, has) the proxy always gets to "virtualize its prototype chain".

I agree, removing the [[Enumerate]] trap would limit a proxy's ability to control the interpretation of its prototype chain. I don't think we should do thjat.

Even if not adopting the changes I just suggested, it seems that we need some harmonization between the proxy traps and the internal [[Enumerate]] signatures.

Indeed. If we stick with the current [[Enumerate]](includePrototype, onlyEnumerable) signature we'll need an ugly preamble for proxies:

  • when includePrototype is false and onlyEnumerable is true, call the keys trap
  • when includePrototype is false and onlyEnumerable is false, call the getOwnPropertyNames trap
  • when includePrototype is true and onlyEnumerable is true, call the enumerate trap
  • any other combination should not occur

With the [[Enumerate]] trap parameters I was trying to minimize the number of essential internal methods. I don't see why we couldn't carry that forward to traps. A single [[GetPropertyKeys]] internal method/trap with includePrototype and onlyEnumerable arguments would seem to cover the all of the use cases of the enumerate, keys and getOwnPropertyNames traps. Why not just have that single trap?

# Tom Van Cutsem (13 years ago)

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

With the [[Enumerate]] trap parameters I was trying to minimize the number of essential internal methods. I don't see why we couldn't carry that forward to traps. A single [[GetPropertyKeys]] internal method/trap with includePrototype and onlyEnumerable arguments would seem to cover the all of the use cases of the enumerate, keys and getOwnPropertyNames traps. Why not just have that single trap?

I prefer the current symmetry with the existing built-ins:

Object.keys(proxy) // triggers handler.keys(target) Object.getOwnPropertyNames(proxy) // triggers handler.getOwnPropertyNames(target)

It's just more learnable and consistent than routing these through a getPropertyKeys trap.

Also, in a previous attempt to merge operations into a single trap (freeze, seal, preventExtensions) we eventually circled back and left them separate, on the grounds that most of the time you'd end up having to dispatch based on the arguments to a dedicated operation anyway.

I'm all for internal spec refactorings to avoid duplication, but I feel this particular refactoring is better left invisible to users.

# Rick Waldron (13 years ago)

On Mon, Oct 8, 2012 at 1:57 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

With the [[Enumerate]] trap parameters I was trying to minimize the number of essential internal methods. I don't see why we couldn't carry that forward to traps. A single [[GetPropertyKeys]] internal method/trap with includePrototype and onlyEnumerable arguments would seem to cover the all of the use cases of the enumerate, keys and getOwnPropertyNames traps. Why not just have that single trap?

I prefer the current symmetry with the existing built-ins:

Object.keys(proxy) // triggers handler.keys(target) Object.getOwnPropertyNames(proxy) // triggers handler.getOwnPropertyNames(target)

It's just more learnable and consistent than routing these through a getPropertyKeys trap.

Also, in a previous attempt to merge operations into a single trap (freeze, seal, preventExtensions) we eventually circled back and left them separate, on the grounds that most of the time you'd end up having to dispatch based on the arguments to a dedicated operation anyway.

Additionally, if your defining a target, you could just as easily define one trap handler and assign it to all traps and do the dispatch anyway (default handler?). I completely agree with the symmetrical approach, anything else will break intuition. (no silver bullet, but counts for something)

# Brendan Eich (13 years ago)

Rick Waldron wrote:

Also, in a previous attempt to merge operations into a single trap
(freeze, seal, preventExtensions) we eventually circled back and
left them separate, on the grounds that most of the time you'd end
up having to dispatch based on the arguments to a dedicated
operation anyway.

Additionally, if your defining a target, you could just as easily define one trap handler and assign it to all traps and do the dispatch anyway (default handler?). I completely agree with the symmetrical approach, anything else will break intuition. (no silver bullet, but counts for something)

Also, double-lifting is a concern. Not sure flag parameters break it, but they are an anti-pattern in themselves.

So what's the right spec/trap layering -- could someone please resummarize?

# Allen Wirfs-Brock (13 years ago)

On Oct 8, 2012, at 10:57 AM, Tom Van Cutsem wrote:

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

With the [[Enumerate]] trap parameters I was trying to minimize the number of essential internal methods. I don't see why we couldn't carry that forward to traps. A single [[GetPropertyKeys]] internal method/trap with includePrototype and onlyEnumerable arguments would seem to cover the all of the use cases of the enumerate, keys and getOwnPropertyNames traps. Why not just have that single trap?

I prefer the current symmetry with the existing built-ins:

Object.keys(proxy) // triggers handler.keys(target) Object.getOwnPropertyNames(proxy) // triggers handler.getOwnPropertyNames(target)

It's just more learnable and consistent than routing these through a getPropertyKeys trap.

Then we should also have the enumerate trap.

Also, in a previous attempt to merge operations into a single trap (freeze, seal, preventExtensions) we eventually circled back and left them separate, on the grounds that most of the time you'd end up having to dispatch based on the arguments to a dedicated operation anyway.

However, internal method have to be dispatched at a very low level of an implementation. In some implementations there might be a runtime cost to having a larger number of them. On the other hand, pushing dispatching into the handler certainly has a cost. I could speculate that these are relatively rare operations (compared to most other internal methods) and if having a larger number of internal methods carried any intrinsic runtime performance cost it would be better to place the cost on the actual use of the internal methods/traps in question.

I'm all for internal spec refactorings to avoid duplication, but I feel this particular refactoring is better left invisible to users.

I definitely want the internal methods and traps to be completely parallel. If we take the path suggested above I will add the equivalent of [[keys]] and [[getOwnPropertyNames]] as essential internal methods and get rid of the parameters to [[Enumerate]].

If that is the consensus, it should probably be captured as a spec. bug report.

# Tom Van Cutsem (13 years ago)

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

Then we should also have the enumerate trap.

Absolutely. I wasn't arguing to get rid of it.

I'm all for internal spec refactorings to avoid duplication, but I feel this particular refactoring is better left invisible to users.

I definitely want the internal methods and traps to be completely parallel. If we take the path suggested above I will add the equivalent of [[keys]] and [[getOwnPropertyNames]] as essential internal methods and get rid of the parameters to [[Enumerate]].

I wouldn't mind if the [[Keys]], [[GetOwnPropertyNames]] and [[Enumerate]] internal methods dispatched to an internal GetPropertyKeys helper function with flags. In fact, why don't we let [[Keys]] and [[GetOwnPropertyNames]] dispatch to a helper parameterized by just an "enumerableOnly" flag. The [[Enumerate]] trap is the only one that does a proto-chain-walk, it probably deserves its own internal method despite a little redundancy in querying the own property names.

To summarize, we'd then have:

[[Keys]] On built-in objects, call helper function GetPropertyKeys(enumerableOnly=true) On proxies, call the "keys" trap

[[GetOwnPropertyNames]] On built-in objects, call helper function GetPropertyKeys(enumerableOnly=false) On proxies, call the "getOwnPropertyNames" trap

[[Enumerate]] On built-in objects, specify an ad hoc algorithm that does the proto-chain-walk On proxies, call the "enumerate" trap

If that is the consensus, it should probably be captured as a spec. bug report.

Ok. Let's first see if everyone can agree to the above.

# Allen Wirfs-Brock (13 years ago)

On Oct 8, 2012, at 11:34 AM, Tom Van Cutsem wrote:

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

Then we should also have the enumerate trap.

Absolutely. I wasn't arguing to get rid of it.

I'm all for internal spec refactorings to avoid duplication, but I feel this particular refactoring is better left invisible to users.

I definitely want the internal methods and traps to be completely parallel. If we take the path suggested above I will add the equivalent of [[keys]] and [[getOwnPropertyNames]] as essential internal methods and get rid of the parameters to [[Enumerate]].

I wouldn't mind if the [[Keys]], [[GetOwnPropertyNames]] and [[Enumerate]] internal methods dispatched to an internal GetPropertyKeys helper function with flags. In fact, why don't we let [[Keys]] and [[GetOwnPropertyNames]] dispatch to a helper parameterized by just an "enumerableOnly" flag. The [[Enumerate]] trap is the only one that does a proto-chain-walk, it probably deserves its own internal method despite a little redundancy in querying the own property names.

Internal abstraction operations (helper functions) are just editorial devices that reduce the need to duplicate (and perhaps introducing errors) specification steps. If it makes sense I'll use one in specifying these traps, but it doesn't make a normative difference to the specification.

# Tom Van Cutsem (13 years ago)

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

Internal abstraction operations (helper functions) are just editorial devices that reduce the need to duplicate (and perhaps introducing errors) specification steps. If it makes sense I'll use one in specifying these traps, but it doesn't make a normative difference to the specification.

I understand. That's why I think it's perfectly OK to use helpers such as GetPropertyKeys internally, but to not expose them via the Proxy API. With my above suggestion, we get a clean spec/trap interface and at the same time less redundancy in the spec.

# Brendan Eich (13 years ago)

Tom Van Cutsem wrote:

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

Internal abstraction operations (helper functions) are just
editorial devices that reduce the need to duplicate (and perhaps
introducing errors) specification steps.   If it makes sense I'll
use one in specifying these traps, but it doesn't make a normative
difference to the specification.

I understand. That's why I think it's perfectly OK to use helpers such as GetPropertyKeys internally, but to not expose them via the Proxy API. With my above suggestion, we get a clean spec/trap interface and at the same time less redundancy in the spec.

Just to make sure, a couple more points to get agreement on:

  • Spec internal helper structure matters, both in terms of aesthetics and best-practices from software engineering, so people will opine on it from time to time and it's fair game for comment.

  • Implementors do look to match the spec where optimization is not critical, even on internal helpers. This is not just spec-following, of course -- the logic driving shared helpers being factored out applies to implementations as well as spec. Or really, spec is just a paper implementation.

# Allen Wirfs-Brock (13 years ago)

On Oct 8, 2012, at 1:21 PM, Brendan Eich wrote:

Tom Van Cutsem wrote:

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>>

Internal abstraction operations (helper functions) are just editorial devices that reduce the need to duplicate (and perhaps introducing errors) specification steps. If it makes sense I'll use one in specifying these traps, but it doesn't make a normative difference to the specification.

I understand. That's why I think it's perfectly OK to use helpers such as GetPropertyKeys internally, but to not expose them via the Proxy API. With my above suggestion, we get a clean spec/trap interface and at the same time less redundancy in the spec.

Just to make sure, a couple more points to get agreement on:

  • Spec internal helper structure matters, both in terms of aesthetics and best-practices from software engineering, so people will opine on it from time to time and it's fair game for comment.

Oh definitely, everything is fair game for comment and discussion. Often both in the spec. and the real world it is a judgement call as to whether or not adding a layer of prodedural abstraction is a beneficial or not. In particular, an extra layer of abstraction doesn't always improve readability and the probability of correct implementation.

  • Implementors do look to match the spec where optimization is not critical, even on internal helpers. This is not just spec-following, of course -- the logic driving shared helpers being factored out applies to implementations as well as spec. Or really, spec is just a paper implementation.

Yes, but it isn't required and as you say, for performance critical functionality directly following the structure of the spec. is much less likely.

BTW, an issue that I have seen in some ES test is an implicit assumption that the implementation factoring and abstraction layering necessarily follows that used by the specification. Just because the spec. uses procedural abstraction to define some functionality doesn't mean that the functionality can be verify by exercising only one of multiple features that depend upon it.

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

BTW, an issue that I have seen in some ES test is an implicit assumption that the implementation factoring and abstraction layering necessarily follows that used by the specification. Just because the spec. uses procedural abstraction to define some functionality doesn't mean that the functionality can be verify by exercising only one of multiple features that depend upon it.

Indeed, such internal details should not be observable -- so were those tests counting on leaky abstractions in certain implementation(s)?

# Allen Wirfs-Brock (13 years ago)

On Oct 8, 2012, at 2:04 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

BTW, an issue that I have seen in some ES test is an implicit assumption that the implementation factoring and abstraction layering necessarily follows that used by the specification. Just because the spec. uses procedural abstraction to define some functionality doesn't mean that the functionality can be verify by exercising only one of multiple features that depend upon it.

Indeed, such internal details should not be observable -- so were those tests counting on leaky abstractions in certain implementation(s)?

For example, assuming that ToNumber of an object calls valueOf can be verified by just writing a test using prefix + and not testing that use case for any other operators.

The problem isn't not so much that ToNumber is a leaky abstraction. It's the assumption that implementations will actually implement it as a call to a common procedure.

If can get away with such assumptions if you are doing white box testing, but not for black box tested based upon a specification.

# Tom Van Cutsem (13 years ago)

2012/10/8 Allen Wirfs-Brock <allen at wirfs-brock.com>

I definitely want the internal methods and traps to be completely parallel. If we take the path suggested above I will add the equivalent of [[keys]] and [[getOwnPropertyNames]] as essential internal methods and get rid of the parameters to [[Enumerate]].

If that is the consensus, it should probably be captured as a spec. bug report.

I filed a spec bug: ecmascript#785