Issues relating to 9.11 The SameValue Algorthm
On Thu, Nov 13, 2008 at 10:31 AM, Allen Wirfs-Brock < Allen.Wirfs-Brock at microsoft.com> wrote:
OK, I now see the difference between SameValue and StrictEquality. SameValue(NaN,NaN) is true and StrictEquality(NaN,NaN) is false. Assuming that we really need this distinction, rather than duplicating "code" I would define one in terms of the other with an additional predicate to handle the NaN distinction.
Yes. And StrictEquality(0, -0) is true whereas SameValue(0, -0) is false. (This is modulo a possible Word rendering error in the Kona draft. This difference on -0 is certainly what was intended, and what I sent to Pratap.)
I also think the SameValue name is misleading as it is really just a StricterEquality test and not a identical value test. This name confusion is probably what caused it to be edited into [[DefineOwnPropety]].
As DavidSarah says, SameValue is an identical value test. The only difference between === and an identical value test is NaN and -0, which are therefore called out as special cases in SameValue.
Finally, I understand the thinking behind using SameValue in Array.prototypeindexOf and Array.prototype.lastIndexOf but do we really want to introduce yet another concept of "equality" into the language just to these functions. If NaN comparision is a special case for these function might not there be other special cases of user defined objects? To me, a better design would be to add a optional third argument to those functions which is a closure that provides the comparison function. That way the default comparison could be === (or == if that makes more sense) and you would search for a NaN by saying something like: [1,2,NaN,3].indexOf(NaN,0,function(x,y) {return IsNaN(x)}). Of course, the first argument is not strictly necessary in this formulation so an even cleaner alternative might me findIndexOf(callbackfn [, fromIndex]) so you could express it as [1,2,NaN,3].FindIndexOf(function(x,y) {return IsNaN(x)})
There's some sense to adding a third argument, but let's not try to squeeze that into ES3.1. In its absence, the default should SameValue, not StrictEquality. These are operations on collections, and should be consistent with the behavior we expect to specify for future containers, specifically Maps.
So, what do the "in the wild" implementations of indexOf do? Do they do a === comparison or do they do the equivalent of the proposed SameValue function?
They do a === comparison. We have recently switched Caja's indexOf and lastIndexOf to SameValue. This change has not disrupted our attempts to port the Prototype and jQuery libraries onto Caja. Nor has it triggered any failures in their regression tests. To accumulate more data, I encourage all those in a position to test to add the following code to some relevant prelude:
(function(){
function identical(x, y) { if (x === y) { // 0 === -0, but they are not identical return x !== 0 || 1/x === 1/y; } else { // NaN !== NaN, but they are identical return isNaN(x) && isNaN(y); } }
Array.prototype.indexOf = function(specimen) { var len = this.length; for (var i = 0; i < len; i += 1) { if (identical(this[i], specimen)) { return i; } } return -1; };
Array.prototype.lastIndexOf = function(specimen) { for (var i = this.length; --i >= 0; ) { if (identical(this[i], specimen)) { return i; } } return -1; }; })();
Thanks.
From: Mark S. Miller [mailto:erights at google.com]
I also think the SameValue name is misleading as it is really just a StricterEquality test and not a identical value test. This name confusion is probably what caused it to be edited into [[DefineOwnPropety]]. As DavidSarah says, SameValue is an identical value test. The only difference between === and an identical value test is NaN and -0, which are therefore called out as special cases in SameValue.
There is at least one case where it is not an identical value test. That is the case where there are multiple implementation dependent NaN encoding as is explicitly allowed for by the specification. Even though == and === (and SameValue) does not distinguish them they are potentially distinguishable by a host object or otherwise using external code ad this distinction can be reported back to ECMAScrpt code. As such, they don't meet my usual criteria as being identical values and in particular I would consider an apparent attempt to use Object.defineProperty to replace a readonly NaN value with a different NaN value encoding to be a violation of the [[Writable]]: false constraint. That's why I don't believe it is appropriate to use SameValue in [[DefineOwnProperty]] .
From: David-Sarah Hopwood
I have no idea what Decimal compareQuietEqual actually does but I
would also
assume that it does something other than an exact same encoding
comparison.
SameValue checks the NaN and zero cases first before using
compareQuietEqual.
My concern (and question) about compareQuietEqual was whether or not it ever returns true for values with distinct representations (for example zeroes with different precisions) that are potentially observably different in some aspect from either external code or ECMAScript code. I don't know that it does but I can imagine that it might.
NaNs (binary and decimal) with distinct internal representations are
not supposed to be observably distinguishable by ECMAScript code; see
section 8.5. That is, the same NaN value at the ECMAScript level can
have different representations at the implementation level (just as it
is possible that the same function value might have different
representations, or a representation that changes if the function is
dynamically compiled, etc.)
As I've said above, I think observably different via calls to external code is enough to call into question the sameness of two values. This is particularly the case when we are dealing with primitive data types like binary and decimal floats that routinely flow across language or other domain boundaries. I'm not particularly concern about language specific types and values (for example, a language specific closure type) that is unlikely to be passed or have meaning across such boundaries. Obviously, an implementation should be free use alternative encoding for the "same" value but it probably needs to cannonicalize such representations when passing them outside its private implementation domain. Unless we require canonicalization for primitive values on calls to/from host objects we probably need to be a little less parochial about what is observable.
From: Mark S. Miller [mailto:erights at google.com] Sent: Thursday, November 13, 2008 12:56 PM To: Allen Wirfs-Brock Cc: es3.x-discuss at mozilla.org; es-discuss at mozilla.org Subject: Re: Issues relating to 9.11 The SameValue Algorthm
On Thu, Nov 13, 2008 at 10:31 AM, Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com<mailto:Allen.Wirfs-Brock at microsoft.com>> wrote:
OK, I now see the difference between SameValue and StrictEquality. SameValue(NaN,NaN) is true and StrictEquality(NaN,NaN) is false. Assuming that we really need this distinction, rather than duplicating "code" I would define one in terms of the other with an additional predicate to handle the NaN distinction. Yes. And StrictEquality(0, -0) is true whereas SameValue(0, -0) is false. (This is modulo a possible Word rendering error in the Kona draft. This difference on -0 is certainly what was intended, and what I sent to Pratap.)
I also think the SameValue name is misleading as it is really just a StricterEquality test and not a identical value test. This name confusion is probably what caused it to be edited into [[DefineOwnPropety]]. As DavidSarah says, SameValue is an identical value test. The only difference between === and an identical value test is NaN and -0, which are therefore called out as special cases in SameValue.
Finally, I understand the thinking behind using SameValue in Array.prototypeindexOf and Array.prototype.lastIndexOf but do we really want to introduce yet another concept of "equality" into the language just to these functions. If NaN comparision is a special case for these function might not there be other special cases of user defined objects? To me, a better design would be to add a optional third argument to those functions which is a closure that provides the comparison function. That way the default comparison could be === (or == if that makes more sense) and you would search for a NaN by saying something like: [1,2,NaN,3].indexOf(NaN,0,function(x,y) {return IsNaN(x)}). Of course, the first argument is not strictly necessary in this formulation so an even cleaner alternative might me findIndexOf(callbackfn [, fromIndex]) so you could express it as [1,2,NaN,3].FindIndexOf(function(x,y) {return IsNaN(x)})
There's some sense to adding a third argument, but let's not try to squeeze that into ES3.1. In its absence, the default should SameValue, not StrictEquality. These are operations on collections, and should be consistent with the behavior we expect to specify for future containers, specifically Maps.
So, what do the "in the wild" implementations of indexOf do? Do they do a === comparison or do they do the equivalent of the proposed SameValue function? They do a === comparison. We have recently switched Caja's indexOf and lastIndexOf to SameValue. This change has not disrupted our attempts to port the Prototype and jQuery libraries onto Caja. Nor has it triggered any failures in their regression tests. To accumulate more data, I encourage all those in a position to test to add the following code to some relevant prelude:
(function(){
function identical(x, y) { if (x === y) { // 0 === -0, but they are not identical return x !== 0 || 1/x === 1/y; } else { // NaN !== NaN, but they are identical return isNaN(x) && isNaN(y); } }
Array.prototype.indexOf = function(specimen) { var len = this.length; for (var i = 0; i < len; i += 1) { if (identical(this[i], specimen)) { return i; } } return -1; };
Array.prototype.lastIndexOf = function(specimen) { for (var i = this.length; --i >= 0; ) { if (identical(this[i], specimen)) { return i; } } return -1; }; })();
Thanks.
On Thu, Nov 13, 2008 at 5:38 PM, Allen Wirfs-Brock < Allen.Wirfs-Brock at microsoft.com> wrote:
From: Mark S. Miller [mailto:erights at google.com]
I also think the SameValue name is misleading as it is really just a StricterEquality test and not a identical value test. This name confusion is probably what caused it to be edited into [[DefineOwnPropety]].
As DavidSarah says, SameValue is an identical value test. The only difference between === and an identical value test is NaN and -0, which are therefore called out as special cases in SameValue.
There is at least one case where it is not an identical value test. That is the case where there are multiple implementation dependent NaN encoding as is explicitly allowed for by the specification. Even though == and === (and SameValue) does not distinguish them they are potentially distinguishable by a host object or otherwise using external code ad this distinction can be reported back to ECMAScrpt code. As such, they don't meet my usual criteria as being identical values and in particular I would consider an apparent attempt to use Object.defineProperty to replace a readonly NaN value with a different NaN value encoding to be a violation of the [[Writable]]: false constraint. That's why I don't believe it is appropriate to use SameValue in [[DefineOwnProperty]] . [...]
As I've said above, I think observably different via calls to external code is enough to call into question the sameness of two values. This is particularly the case when we are dealing with primitive data types like binary and decimal floats that routinely flow across language or other domain boundaries. I'm not particularly concern about language specific types and values (for example, a language specific closure type) that is unlikely to be passed or have meaning across such boundaries. Obviously, an implementation should be free use alternative encoding for the "same" value but it probably needs to cannonicalize such representations when passing them outside its private implementation domain. Unless we require canonicalization for primitive values on calls to/from host objects we probably need to be a little less parochial about what is observable.
Well, I'm glad at least to see realization of the significance of this issue, although I arrive at a different conclusion. In ES itself (ignoring decimal for now), there really is only one NaN value. If a call to [[DefineOwnProperty]] may be rejected because a presented NaN was not identical enough to a stored NaN, even though they do not differ in any way detectable or explicable to a JavaScript programmer, then we may as well instead specify that [[DefineOwnProperty]] just randomly succeeds or fails at the implementation's whim.
I do not think it is productive to imagine that we can evade our responsibility as language designers to say what the potentially distinguishable values are in our semantic state. No such decision will be satisfying. Any decision will have unfortunate consequences. But not deciding is worse.
OK, I now see the difference between SameValue and StrictEquality. SameValue(NaN,NaN) is true and StrictEquality(NaN,NaN) is false. Assuming that we really need this distinction, rather than duplicating "code" I would define one in terms of the other with an additional predicate to handle the NaN distinction.
I also think the SameValue name is misleading as it is really just a StricterEquality test and not a identical value test. This name confusion is probably what caused it to be edited into [[DefineOwnPropety]].
Finally, I understand the thinking behind using SameValue in Array.prototypeindexOf and Array.prototype.lastIndexOf but do we really want to introduce yet another concept of "equality" into the language just to these functions. If NaN comparision is a special case for these function might not there be other special cases of user defined objects? To me, a better design would be to add a optional third argument to those functions which is a closure that provides the comparison function. That way the default comparison could be === (or == if that makes more sense) and you would search for a NaN by saying something like: [1,2,NaN,3].indexOf(NaN,0,function(x,y) {return IsNaN(x)}). Of course, the first argument is not strictly necessary in this formulation so an even cleaner alternative might me findIndexOf(callbackfn [, fromIndex]) so you could express it as [1,2,NaN,3].FindIndexOf(function(x,y) {return IsNaN(x)})
So, what do the "in the wild" implementations of indexOf do? Do they do a === comparison or do they do the equivalent of the proposed SameValue function?
From: es3.x-discuss-bounces at mozilla.org [mailto:es3.x-discuss-bounces at mozilla.org] On Behalf Of Allen Wirfs-Brock Sent: Thursday, November 13, 2008 8:27 AM To: erights at google.com Cc: es3.x-discuss at mozilla.org Subject: Issues relating to 9.11 The SameValue Algorthm
The SameValue algorithm was recently added as section 9.11. I think you did or requested it. Unless I'm missing something, this algorithm appears to compute the same result as the Strict Equality Comparison Algorithm in section 11.9.6. Am I missing something?? Is there some reason we need both of these?
One of the places that a call to SameValue was inserted was into the algorithm of [[DefineOwnProperty]]. I believe that this change unintentionally changes at least one part of that algorithm. The problem is in step 10.a.ii.1 which is now written to do a SameValue comparison between the existing [[Value]] attribute of a property and the [[Value]] attribute of the property descriptor. This is in a clause that is predicated by the [[Writable]] field of the property being false. The intent of this is that ECMAScript level could can get a property descriptor for some readonly property (the property descriptor object will include a value: property) modify some attributes in the descriptor (for example, the configurable attribute) and then use that same descriptor in a call to defineProperty which ultimately uses [[DefineOwnProperty]]. This is OK for a readonly property as long as the value in the descriptor is the exact same value as the current value of the property (in other words the value is not being changed). The problem with SameValue is that it does not test for the exact same value. Its use in [[DefineOwnProperty]] would, for example, let a read only +0 be replaced with a -0 or replace a NaN with one implementation dependent encoding with a NaN with another encoding. I have no idea what Decimal compareQuietEqual actually does but I would also assume that it does something other than an exact same encoding comparison.
The other uses of SameValue in [[DefineOwnProperty]] is probably ok as there are operating upon getter and setter function values and there is a precondition that these values will be either undefined or a IsCallable object.
I actually don't think there was necessarily a problem with the original algorithm that simply said that they must be the "same value". In this context, "value" is as defined in Section 8 and "same" has its common English meaning. It does mean "equal" or "equivalent".