On Jul 29, 2013, at 9:06 AM, Tom Van Cutsem wrote:
> Hi,
>
> Originally, direct proxies were specified such that a direct proxy would
> inherit the [[Class]] of the target object it wraps. The intent was for e.g.
> proxies-for-arrays to be recognized properly as arrays.
>
> In ES6 the [[Class]] property is gone and the current draft instead uses
> wording such as "if O is an exotic Array object..."
>
> My understanding is that a proxy for an array would not pass such a test,
> unless we explicitly state otherwise.
yes that's correct
>
> Allen, could you clarify your intent?
What you are running into is very closely related to vthe private state access
issues that led us to define DelegatingHandler/ForwardHandler, etc. The
difference here is that you are concerned about external behavioral branding
rather than internal implementation branding of the sort that occurs for
private state access.
The legacy [[Class]] internal property conflated these two concepts. Sometimes
it was used for to ensure that a built-in method was operating upon an instance
that actually had the internal state or conformed to other implementation level
invariants needed by the method. Other times, [[Class]] was tested for basic
external behavioral classification purposes that don't really care at all about
implementation level object invariants.
In most cases, the ES6 spec. language such as "if O is an exotic X object" or
"does X have a [[XXX]] internal data property" works fine as a direct
replacement of an ES5 [[Class]] test because there are a one-to-one correspond
between a ES6 built-in that is represented by specific kind of exotic object or
that has a specific internal data property and with a ES5 built-in with the
corresponding [[Class]] value.
However, a proxy on such a built-in does not pass such a test. A Proxy object
whose target is an Array is an exotic proxy object, not an exotic array object.
It would be unsafe to allow such a proxy to be recognized an an exotic array
regardless of whether it was done via the ES6 spec. language or via
[[Class]]==Array if we still had it. A proxy for an array and an array is not
likely to have the same implementation shape. A method that tests for an
exotic array object might should be able to assume, for example, that its
implementation depended encoding of the "length" property is present. Disaster
would follow if the method tried to operate upon a proxy instance instead of an
actual exotic array instance.
The general solution to this problem is to decouple the shape branding/testing
from the behavior testing. This is already done a number of places in the ES6
spec to support subclassing flexibility.
For example, a number of the ES<=5 String method such as "replace" have
different behavior depending upon whether an argument is a RegExp or a string.
In the ES6 spec. the [[Class]]==RegExp test is replaced by an HasProperty test
for @@isRegExp and and a form of double dispatching. This permits subclasses
of RegExp (or even alternative regular expression engines that don't inherit
from the built-in RegExp) to be used in that argument position. It may also be
transparent to proxying (depending upon the handler).
In general, an case by case analysis needs to be done for each of the
situations where [[Class]] is used in ES5 for behavioral checking to se if and
how an alternative needs to be provided for ES6.
>
> If proxies for arrays do not pass such tests, some built-ins behave in
> unexpected ways.
What's expected? Just because a proxy has an exotic array object as its target
doesn't mean that is functions as an exotic array.
>
> Here's the list of relevant built-ins, based on searching the ES6 spec for
> "is an exotic Array object":
> * Object.prototype.toString
As currently spec'ed. toString will invoke the HasProperty(@@toStringTag) of
the proxy and if the result is true invoke the Get trap to retrieve the value
used to construct the toString result. All of the new built-ins have
@@toStringTag methods. I considered adding them for the built--ins that carry
over from ES5. Then, if the @@toStringTag of Array.prototype is "Array" then
Object.prototype.toString(new Proxy([ ], { })) should return "[Object Array]".
However, this would be a breaking change for existing code explicitly wires
their prototype chain to inherit from Array.prototype.
> * Array.isArray
We've discussed the meaning of this in the past and have agreed that it should
be considered a test for exotic array-ness and in particular the length
invariant. A Proxy whose target is an exotic array may or may not qualify. We
could add a @@isArray tag, but I'm not convinced that it is really needed.
> * Array.prototype.concat
As currently spec'ed the [[Class]] test has been replaced with an exotic array
test on the this value (supporting creating subclass instances) and a
IsConcatSpreadable abstract operation (defined in terms of
@@isConcatSpreadable) falling back to an exotic array test) used to decide
whether to spread argument values.
To make concat spread a proxied array you would have to explicitly define the
@@isConcatSpreadable property of the target object. To maintain legacy
computability with objects inheriting from Array.prototype we can't just have
include @@isConcatSpreadable on Array.prototype.
There may be other teaks we could do. But we talked about concat at last weeks
meeting WRT Typed Arrays and concluded that concat is pretty bogus and it may
be a turd that is not worth polishing.
> * JSON.stringify
There are two distinct use cases of [[Class]]==Array in the ES5 spec of this
function. Both are currently in the ES6 spec. as exotic array tests. The
first use case is to see if the "replaceer" argument is an white-list array.
This could be special cased via a @@isJSONArray that would work through a
proxy, but I dubious that the additional complexity is justified.
The other use case is when deciding whether to use { } or [ ] notation to
encode the object. The exotic array test is the right one for ES5
compatibility. The appropriate JSON encoding of a Proxy that has an exotic
array target is going to be application dependent. JSON.parse already has an
extensibility hook (toJSON) that presumably should be sufficient to deal with
such objects.
> * JSON.parse
I believe that this [[Class]]==Array (now is exotic array) test is only applied
to objects created by evaluating the JSON text as if it was an object literal.
Such objects will never be proxies.
> * ArrayBuffer.isView
yup. The use cases for isView aren't all that clear to me. It could be
expressed a @@isView test if it has important use cases.
>
> If we don't make proxies-for-arrays work transparently, we get results such
> as:
>
> var p = new Proxy( [1,2] , {} );
Yes, and the only reason Array.prototype methods will work is because they have
all been carefully defined to be generic and not depend upon any internal array
state. The methods of most other built-ins will fail if you create an object
in this manner because it defaults to delegating rather than forwarding.
>
> Object.prototype.toString.call(p) // "[object Object]", expected "[object
> Array]"
> Array.isArray(p) // false, expected true
> [0].concat(p) // [0,p], expected [0,1,2]
> JSON.stringify(p) // "{\"0\":1,\"1\":2}", expected "[1,2]"
>
> Note that none of these built-ins actually relies upon some structural array
> invariant. Passing in a proxy that has properties such as "length", "0", etc.
> works equally well.
see above...
>
>
> It's worth noting that I hit upon these issues because users of my
> harmony-reflect shim, which are using direct proxies today in ES5, have
> reported them (see [1],[2]). This adds some evidence that users expect the
> above built-ins to behave transparently w.r.t. proxies for their use cases.
> My library patches some of these built-ins to recognize my own emulated
> proxies, but this is just an ad hoc solution. ES6 users will obviously not be
> able to do this.
They may expect this, but I don't see what generalizations we can make.
Whether a proxy over a built-in is behaviorally substitutable for the built-in
completely dependent upon the the definition of the specific proxy. I think
this is just something that anybody who uses Proxy needs to be aware of.
Allen
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss