New topic regarding Proxies: intercession for ===

# Brendan Eich (15 years ago)

David Ungar raised this as a question from the audience after Tom's Proxies talk at DLS. One would expect no less from David, who indeed cited Self, which allows more intercession than harmony:proxies enables.

This is not something I'm trying to get into harmony:proxies, but I thought it deserved more discussion, so here in es-discuss seems like a good place.

One reason not to allow === to be trapped is mutation. If two object references are ===, then today we know they reference the same object. If that object is not frozen, then we expect to see effects through both references. If frozen, no effects can be committed or viewed via any reference.

If unfixed proxies could be === to unfrozen objects, then the proxy's handler could do whatever it wants and violate these old semantics.

The value types idea, building on proxies, will want to support a value proxy that can intercede for === and most of the other operators. See slang.soe.ucsc.edu/cormac/proxy.pdf (which is a tour de force that covers other applications than value types!).

But value types, we think, would be frozen and compared by contents -- property names and property values, recursively down the typically-shallow, frozen tree that represents the value. Details TBD, but this solves the mutation issue.

So perhaps the answer to David's DLS question is twofold: "we chickened out for now; we're fixing for a subset of all objects later (maybe)".

Still not the crisp and Self-ish answer one might hope for, but that is JS :for you :-/. So I went back to the Self well:

4.8 Equality, Identity, and Indistinguishability

Equality, identity, and indistinguishability are three related concepts that are often confused. Two objects are equal if they “mean the same thing”. For example, 3 = 3.0 even though they are different objects and have different representations. Two objects are identical if and only if they are the same object. (Or, more precisely, two references are identical if they refer to the same object.) The primitive _Eq: tests if two objects are identical. Finally, two objects are indistinguishable if they have exactly the same behavior for every possible sequence of non-reflective messages. The binary operator “==” tests for indistinguishability. Identity implies indistinguishability which implies equality.

It is actually not possible to guarantee that two different objects are indistinguishable, since reflection could be used to modify one of the objects to behave differently after the indistinguisability test was made. Thus, == is defined to mean identity by default. Mirrors, however, override this default behavior; (m1 == m2) if (m1 reflectee _Eq: m2 reflectee). This makes it appear that there is at most one mirror object for each object in the system. This illusion would break down, however, if one added mutable state to mirror objects.

[From labs.oracle.com/self//release_4.0/Self-4.0/manuals/Self-4.1-Pgmers-Ref.pdf]

For JS, we don't have a clean equality analogue. One might be tempted to say that equality is ==, identity is === (ignoring -0 === 0 and NaN !== NaN), and indistinguishability is Object.eq (name still not settled on), from

strawman:egal

But JS == is not even an equivalence relation, and -0 is not identical to 0 operationally even if one buys into the IEEE-754 standard and numerical programming precedents for NaN !== NaN.

So it is more accurate to say JS has === for equality, it can have Object.eq for identity (self-hosted today on top of ===), and it could have indistinguishability.

Should we consider indistinguishability on the Self model, with proxies instead of mirrors, and any other necessary changes?

JS will never be as simple as Self, but with proxies and value types based on them, it seems we might have get very close to the "right" answer to David's question.

# P T Withington (15 years ago)

On 2010-10-27, at 17:09, Brendan Eich wrote:

JS will never be as simple as Self, but with proxies and value types based on them, it seems we might have get very close to the "right" answer to David's question.

2p. from the Dylan POV: Dylan only had equality and identity (thinking Lisp had just way too many equivalences). Dylan's MOP let you override (the equivalent of) new and ==, but not ===. If you wanted value objects that were indistinguishable, you wrote a new implementation that always returned the identical object for parameters that would otherwise create == values (using a weak-key table). If you only cared about equality, you wrote a == method that implemented your equality test. You chose based on whether you expected to do more constructing or more comparing.

Is a proxy enough of a power tool that you just have to warn the user they must know what they are doing to use it? I.e., if you override the MOP in some inconsistent way, it's not our fault?

# Brendan Eich (15 years ago)

On Oct 27, 2010, at 4:12 PM, P T Withington wrote:

On 2010-10-27, at 17:09, Brendan Eich wrote:

JS will never be as simple as Self, but with proxies and value types based on them, it seems we might have get very close to the "right" answer to David's question.

2p. from the Dylan POV: Dylan only had equality and identity (thinking Lisp had just way too many equivalences). Dylan's MOP let you override (the equivalent of) new and ==, but not ===. If you wanted value objects that were indistinguishable, you wrote a new implementation that always returned the identical object for parameters that would otherwise create == values (using a weak-key table). If you only cared about equality, you wrote a == method that implemented your equality test. You chose based on whether you expected to do more constructing or more comparing.

We talked about this during the decimal discussions in past TC39 meetings, but hash-cons'ing decimal non-literals, esp. intermediate results in arithmetic expression evaluations, is too expensive for hardcore numerical performance folks.

If value types are frozen all the way down and they bottom out soon enough, then comparing references or (if those do not match) referents, byte-wise and deeply, should be enough for default ===. It won't handle -0m === 0m or 0m/0m !== 0m/0m (the NaN in decimal !== itself). At one time we thought we could deviate from IEEE 754r on those fine points. Probably === needs to be meta-programmable to capture all the possibilities.

Is a proxy enough of a power tool that you just have to warn the user they must know what they are doing to use it? I.e., if you override the MOP in some inconsistent way, it's not our fault?

Yes, as "The Art of the Meta-Object Protocol" makes clear, you can't avoid some sharp edges on these power tools. This is not an excuse for avoidable unsafety of course. As noted, overriding Object.prototype.toString and other such built-ins is a bad idea too.

Proxies, like host objects in real browsers, can produce nonsense answers, but ES5 tightens up the language about what is legal per-spec. Proxies don't introduce overt lack of safety, but they do mean code that thought a[i] was never running a function (a handler trap) might have its expectations violated. But getters and setters already rocked this boat.

This is why for-in should be metaprogrammable, or really: objecting to for-in being programmable by a Proxy's handler iterate trap is objecting too late and selectively, when the get and set horses (ES3R, or ES5 now), and the rest of the harmony:proxies trap-horses (12 in total so far, excluding iterate), have already left the barn.

Proxies are power tools. Client code that may wind up using them, even old code written before their advent, will expect them to behave like native objects (or "good" host objects).

This puts pressure on proxy implementors, and the JS library and client-code ecosystem will have to sort good from bad proxy implementations.

TC39 certainly can't guarantee no bad proxies are written, except by renouncing metaprogramming -- which we have not done in ES5 (or the "ES3R" ES3 + reality that ES5 drew on), and will not do (in Harmony, per plans on the wiki). Renunciation in favor of the safety of pre-1999 JS (no getters or setters) so leaves us with only the browser implementors and their host objects as the privileged mess-makers. From a purely Machiavellian point of view, we want to level this playing field.

It's a scary world, but you're better off with user-level metaprogramming in JS, compared to a world of only "kernel-level" metaprogramming in typically C++ host object codebases. At least, I think that's TC39's position. It certainly is mine.

# Tom Van Cutsem (15 years ago)

2010/10/27 Brendan Eich <brendan at mozilla.com>

David Ungar raised this as a question from the audience after Tom's Proxies talk at DLS. One would expect no less from David, who indeed cited Self, which allows more intercession than harmony:proxies enables.

This is not something I'm trying to get into harmony:proxies, but I thought it deserved more discussion, so here in es-discuss seems like a good place.

One reason not to allow === to be trapped is mutation. If two object references are ===, then today we know they reference the same object. If that object is not frozen, then we expect to see effects through both references. If frozen, no effects can be committed or viewed via any reference.

Just to avoid confusion: by frozen, you mean deeply immutable here, right? (since ES5-frozen objects can still be mutable, in which case the above statement does not hold)

If unfixed proxies could be === to unfrozen objects, then the proxy's handler could do whatever it wants and violate these old semantics.

The value types idea, building on proxies, will want to support a value proxy that can intercede for === and most of the other operators. See slang.soe.ucsc.edu/cormac/proxy.pdf (which is a tour de force that covers other applications than value types!).

But value types, we think, would be frozen and compared by contents -- property names and property values, recursively down the typically-shallow, frozen tree that represents the value. Details TBD, but this solves the mutation issue.

So perhaps the answer to David's DLS question is twofold: "we chickened out for now; we're fixing for a subset of all objects later (maybe)".

Still not the crisp and Self-ish answer one might hope for, but that is JS :for you :-/. So I went back to the Self well:

4.8 Equality, Identity, and Indistinguishability

Equality, identity, and indistinguishability are three related concepts that are often confused. Two objects are equal if they “mean the same thing”. For example, 3 = 3.0 even though they are different objects and have different representations. Two objects are identical if and only if they are the same object. (Or, more precisely, two references are identical if they refer to the same object.) The primitive _Eq: tests if two objects are identical. Finally, two objects are indistinguishable if they have exactly the same behavior for every possible sequence of non-reflective messages. The binary operator “==” tests for indistinguishability. Identity implies indistinguishability which implies equality.

It is actually not possible to guarantee that two different objects are indistinguishable, since reflection could be used to modify one of the objects to behave differently after the indistinguisability test was made. Thus, == is defined to mean identity by default. Mirrors, however, override this default behavior; (m1 == m2) if (m1 reflectee _Eq: m2 reflectee). This makes it appear that there is at most one mirror object for each object in the system. This illusion would break down, however, if one added mutable state to mirror objects.

[From labs.oracle.com/self//release_4.0/Self-4.0/manuals/Self-4.1-Pgmers-Ref.pdf ]

For JS, we don't have a clean equality analogue. One might be tempted to say that equality is ==, identity is === (ignoring -0 === 0 and NaN !== NaN), and indistinguishability is Object.eq (name still not settled on), from

strawman:egal

But JS == is not even an equivalence relation, and -0 is not identical to 0 operationally even if one buys into the IEEE-754 standard and numerical programming precedents for NaN !== NaN.

So it is more accurate to say JS has === for equality, it can have Object.eq for identity (self-hosted today on top of ===), and it could have indistinguishability.

Should we consider indistinguishability on the Self model, with proxies instead of mirrors, and any other necessary changes?

I'm not sure I understand completely. Do we need 'indistinguishability' as a third concept now that we have proxies that can appear to be other objects? Let me clarify:

Assume p1 and p2 are two proxies that both proxy for an object o. Assume also that these proxies want to appear to be indistinguishable from o (they will faithfully behave like o for all meta-level operations). In that case I can imagine it would be nice if there were a comparison operator that they could trap to uphold the illusion of indistinguishability, both between p1 and p2 and between either p{1|2} and o.

As I see it, if Object.eq is introduced as the solid identity test that cannot be fooled/intercepted, we could consider trapping '==='. Then p1 === p2 and p{1|2} === o could be made to return true. Object.eq will still reveal their separate identities.

Btw, while browsing the Self programmer's manual, I also stumbled upon section 4.1, which makes the observation that "Programs that depend on object identity are also reflective". When viewed in this context, Object.eq is a reflective operator, so it really should reveal proxies.

# Mike Samuel (15 years ago)

2010/10/27 Brendan Eich <brendan at mozilla.com>:

On Oct 27, 2010, at 4:12 PM, P T Withington wrote:

On 2010-10-27, at 17:09, Brendan Eich wrote:

JS will never be as simple as Self, but with proxies and value types based on them, it seems we might have get very close to the "right" answer to David's question.

2p. from the Dylan POV:  Dylan only had equality and identity (thinking Lisp had just way too many equivalences).  Dylan's MOP let you override (the equivalent of) new and ==, but not ===.  If you wanted value objects that were indistinguishable, you wrote a new implementation that always returned the identical object for parameters that would otherwise create == values (using a weak-key table).  If you only cared about equality, you wrote a == method that implemented your equality test.  You chose based on whether you expected to do more constructing or more comparing.

We talked about this during the decimal discussions in past TC39 meetings, but hash-cons'ing decimal non-literals, esp. intermediate results in arithmetic expression evaluations, is too expensive for hardcore numerical performance folks. If value types are frozen all the way down and they bottom out soon enough, then comparing references or (if those do not match) referents, byte-wise and deeply, should be enough for default ===. It won't handle -0m === 0m or

I can see why decimal needs to have multiple zero values since decimal values carry a precision. But can implementors of custom numeric types reuse the existing NaN value? If so, then equality only needs to be programmable to allow non-identical values to compare as equals, not to introduce any new non reflexively equal values. That might be a simpler problem to address.