test suite for testing shallow & deep, strict equality as provided by various libraries
npm install jseq
Table of Contents generated with DocToc
- Breaking News
- Should Key Ordering Matter in Objects?
- ECMAScript6 Classes and Type Checking
- Segue on The Miller Device
- Deep Equality acc to deep-equal-ident
- jsEq
- Language Choice and Motivation
- Test Module Setup
- Implementations Module Setup
- Equality, Identity, and Equivalence
- First Axiom: Value Equality Entails Type Equality
- Equality of Sub-Types
- Equality of Numerical Values in Python
- Second Axiom: Equality of Program Behavior
- Infinity, Positive and Negative Zero
- Not-A-Number
- Object Property Ordering
- Properties on 'Non-Objects'
- Primitive Values vs Objects
- Undefined Properties
- Functions (and Regular Expressions)
- How Many Methods for Equality Testing?
- Plus and Minus Points
- Benchmarks
- Libraries Tested
- Caveats and Rants
* key ordering used to be an accidental property of JS objects
* constance of key ordering (key ordering by definition order) always used to be silently implemented by
most popular engines because reasons
* now it's in ES XXX so it's official
* question is, should x = { a: 1, b: 1, }, y = { b: 1, a: 1, } be equal or not equal?
conventionally key ordering is considered irrelevant for equality, so x eq y should hold*
but sometimes key ordering is used, so x eq y should fail*
* propose to agree upon setting a symbol (Symbol.x_orderedkeys) to signal objects that should be keeping
key ordering (tested as x[Symbol.x_orderedkeys]?)
* deriving from specific class (class OrderedKeysObject extends Object) possible but better not done as it
would conflict with other more important uses of custom classes
* what to do if x has that property but y lacks it?
* always reject equality because 'x has something that y is lacking', or else
* assume that x_orderedkeys is 'contagious' but not 'dominant', i.e. presence in any one value mandates
checking for ordered keys
* should also be possible to configure test whether to check for this, or use special method
(eq_with_ordered_keys())
> UPDATE this topic is dealt with in more detail in
> InterType.
Whenever one thinks one has tamed the utter madness that is JavaScript's type system, one can be reasonably
sure another one of the Hydra's ugly heads is waiting right behind the corner. This happens with ECMAScript6
Classes.
Let us go on a Journey in Five Parts where I'd like to define a class that extends JS Array; I thentypeof
instantiate it and poke at it with all the sonic screwdrivers I have. This looks good until I use either or the old trusty (but, by now, a bit rusty) [Miller Device]() to ascertain the class name of that
thing:
`coffeePreface. Packing for the Journey.
---------------------------------
types = new ( require 'intertype' ).Intertype() # https://github.com/loveencounterflow/intertype
class Myclass extends Array
d = new Myclass() # in REPL, correctly echoes Myclass(0) [], 0 being array length
Array.isArray d # true, no problemtrue
d instanceof Array # , no problemtrue
d instanceof Myclass # , no problemtrue
types.isa.list d # , no problem
typeof d # 'object'; NB that ( typeof [] ) == 'object'Array
types.type_of d # 'list' (our name for JS instances)
Object::toString.call d # 'Miller Device', gives '[object Array]'
d.constructor.name # 'Myclass'! Yay!
`
Turns out only d.constructor.name does the trick—let's call it the Dominic Denicola
Device since he wrote the top-rated SO
answer to this pressing question back in 2015.
So let's try and see what the DDDevice can do for us.
In essence, we just need to set up a function ddd = ( x ) -> x.constructor.name; the only problem withnull
that is of course that checking attributes on and undefined will fail loudly (as if JS ever cared
but whatever), so we have to safeguard against that; these two definitions are equivalent:
`coffee`
ddd = ( x ) -> if x? then x.constructor.name else ( if x is null then 'null' else 'undefined' )
ddd = ( x ) -> x?.constructor.name ? ( if x is null then 'null' else 'undefined' )
Our ddd() method does give reasonable answers (for a JS type detecting method):
``
ddd {} # 'Object'
ddd [] # 'Array'
ddd null # 'null'
ddd true # 'Boolean'
ddd 42 # 'Number'
ddd NaN # 'Number'
ddd Infinity # 'Number'
ddd ( new Myclass() ) # 'Myclass'
#### Segue on The Miller Device
A code comment from 2010 ([CND Types module]()):
> It is outright incredible, some would think frightening, how much manpower has gone into reliable
> JavaScript type checking. Here is the latest and greatest for a language that can claim to be second
> to none when it comes to things that should be easy but aren’t: the ‘Miller Device’ by Mark Miller of
> Google (http://www.caplet.com), popularized by James Crockford of Yahoo!.*
>
> As per https://groups.google.com/d/msg/nodejs/P_RzSyPkjkI/NvP28SXvf24J, now also called the 'Flanagan
> Device'
>
> * http://ajaxian.com/archives/isarray-why-is-it-so-bloody-hard-to-get-right
> * http://blog.360.yahoo.com/blog-TBPekxc1dLNy5DOloPfzVvFIVOWMB0li?p=916 # page gone
> * http://zaa.ch/past/2009/1/31/the_miller_device_on_null_and_other_lowly_unvalues/ # moved to:
> * http://zaa.ch/post/918977126/the-miller-device-on-null-and-other-lowly-unvalues
GitHub user fkling has recently published
deep-equal-ident, where he wants to "track[...] the identity
of nested objects". That may sound a bit cryptic at first, but should become clear when considering two
example test cases:
`coffee #1
a = [ 1, 2, 3, ]
b = [ 1, 2, 3, ]
foo = [ a, a, ]
bar = [ b, b, ]
deepEqualIdent foo, bar # true
deepEqualIdent foo, bar # false
`
The gist of the comparison policy of deep-equal-ident is this: for two objects foo, bar to be deeplyfoo
equal, we should expect that they contain deeply equal values at the same indices. now, if we inspect bar
and , we find that both objects in both cases do have x[ 0 ][ 0 ] == 1, x[ 0 ][ 1 ] == 2, x[ 1 ][
0 ] == 1, and so on, so at a glance, foo and bar should be considered deeply equal.
However, we've all learned in school that when you have an equation like x == y * 4, that equation must
still hold when transformed in a way that does the same to the left and the right hand term; for example,
you might want to divide both sides by four, which gives you
``
x y * 4
x = y * 4 => ——— == ——————— => x / 4 == y
4 4
So far so good. Now consider what happens in test case #2 when we manipulate a or b. We start out withfoo == bar
assuming . Then we perform the same operation on the left and the right hand side, say
``
| foo[ 1 ][ 0 ] += 1
foo == bar => | => ?
| bar[ 1 ][ 0 ] += 1
Does equality still hold or has it been violated? Indeed, the latter is the case: when we say foo[ 1 ][ 0 ]
+= 1, we're really doing a[ 0 ] += 1, since foo[ 1 ] is a (foo[ 1 ] is identical to a). But a isfoo
also referenced as the first element of , so foo[ 0 ] changes along with foo[ 1 ], which meansfoo is now [ [ 2, 2, 3, ], [ 2, 2, 3, ], ].
OK great, what with bar? Doing bar[ 1 ][ 0 ] += 1 gives [ 2, 2, 3, ] alright, but in thisbar[ 0 ]
case, remains unaffected—meaning when we print out foo and bar, what we see is
``
[ [ 2, 2, 3, ], [ 2, 2, 3, ], ] # foo
[ [ 1, 2, 3, ], [ 2, 2, 3, ], ] # bar
which, obviously, does not describe two deeply equal objects.
There are a couple of related, recurrent and, well, relatively 'deep' problems that vex many people who
program in JavaScript on a daily basis, and those are sane (deep) equality testing, sane **deep
copying, and sane type checking**.
jsEq attempts to answer the first of these questions—how to do sane testing for deep equality in JavaScript
(specifically in NodeJS)—by providing an easy to use test bed that compares a number of libraries that
purport to deliver solutions for deep equality. It turns out that there are surprising differences in detail
between the libraries tested, as the screen shot below readily shows (don't take the qunitjs testnode jseq/lib/main.js
seriously, those are currently broken due to the (to me at least) strange API of that library).
Here is a sample output of jsEq running :
!Output of node jseq/lib/main.js
> Implementations whose key starts with a * are 'hobbyists solutions' that i have gleaned
> from blogs and answers on StackOverflow.com; they're included mainly to show how well one can expect
> an ad-hoc solution can be expected to work (quite well in some cases, it turns out). See the
> list of tested libraries and the caveats section.
The lodash and underscore results are probably identical because lodash strives to be a 'betterunderscore'.
> At first it may be hard to see what ne +0, -0 could be useful for as in JavaScript, +0 == -0 holds,
> but see below for
> Infinity, Positive and Negative Zero
The jkroso equals and CoffeeNode Bits'N'Pieces results are identical since the former is really the
implementation of the latter; based on the results shown i'll try to devise a solution that combines
different libraries and passes all tests.
It has to be said that while—as it stands—jsEq will run no less than 12 * 212 == 2544 tests, most tests==
are between primitive values, which explains why bot JS and === turn in with around 9 out of 10 tests
passed.
The present module has been implemented in CoffeeScript, my favorite language
these days. Most of the examples in the present ReadMe are in CoffeeScript, too, so whenever you see
a construct like f x, y, you'll have tof( x, y )
mentally translate that into . What's more, CoffeeScript's == operator translates to===
JavaScript's , while JS == has (rightly) no equivalent in CS. I agree that this can be==
confusing, especially in a text like this where different concepts of equality play a crucial role. I
strive for clarity in this point by making sure that whenever an isolated or === appears, it is
annotated from which language it has been taken.
> I for one prefer to use : for assignment=
> (as it is already done inside object literals) and for equality testing, which is one of the reasons
> i started Arabika, an as yet incipient and experimental
> language where i try out parametrized, modular grammars (so that if you like the language but can't live
> with my particular choice for the equals operator, you can instantiate your own grammar with your
> own choice for that one).
Incidentally, i'm writing lots of tests for Arabika, and one day i was struck
by a false positive when a parsing result à la [ 3 ] passed the comparison to [ '3' ].assert.deepEqual
Research quickly showed NodeJS' to be the culprit, so i chimed in to the
discussion on bug #7161. I felt i was not completely alone
in my quest for sound equality testing in JavaScript, and the subject being too complex to grasp with
haphazard, isolated ad-hoc tests issued via the NodeJS REPL, i came up with jsEq: it is not a new
implementation of JS (deep) equality, but rather an extensible framework to test available software that
purports to have an answer to the vexing problem whether two given values are or are not equal.
Test cases are set up in the src/implementations.coffee modules, inside a function that accepts twoeq
functions , ne and returns an object of test cases. Test case names are short descriptions of what
they test for and are used to produce a legible output.
Tests that run a single test should return the result of applying the provided eq or ne to test for theeq
current implementation's concept of equality with or inequality with ne aginst a pair of values.
Tests that run multiple subtests should return a pair [ n, errors, ] where n is the subtest count anderrors is a list with a meaningful message for each failed subtest (the individual messages from suchsrc/implementations.coffee
'mass testing facilities' are currently not shown, as they produce a lot of output, but that will probably
be made a matter of configuration). Here's what currently looks like:
`coffeescript
#-----------------------------------------------------------------------------------------------------------
module.exports = ( eq, ne ) ->
R = {}
### 1. simple tests ###
#---------------------------------------------------------------------------------------------------------
### 1.1. positive ###
R[ "NaN equals NaN" ] = -> eq NaN, NaN
R[ "finite integer n equals n" ] = -> eq 1234, 1234
R[ "emtpy array equals empty array" ] = -> eq [], []
R[ "emtpy object equals empty object" ] = -> eq {}, {}
#---------------------------------------------------------------------------------------------------------
### 1.2. negative ###
R[ "object doesn't equal array" ] = -> ne {}, []
R[ "object in a list doesn't equal array in array" ] = -> ne [{}], [[]]
R[ "integer n doesn't equal rpr n" ] = -> ne 1234, '1234'
R[ "empty array doesn't equal false" ] = -> ne [], false
R[ "array with an integer doesnt equal one with rpr n" ] = -> ne [ 3 ], [ '3' ]
#=========================================================================================================
### 2. complex tests ###
#---------------------------------------------------------------------------------------------------------
R[ "circular arrays with same layout and same values are equal (1)" ] = ->
d = [ 1, 2, 3, ]; d.push d
e = [ 1, 2, 3, ]; e.push d
return eq d, e
#---------------------------------------------------------------------------------------------------------
R[ "circular arrays with same layout and same values are equal (2)" ] = ->
d = [ 1, 2, 3, ]; d.push d
e = [ 1, 2, 3, ]; e.push e
return eq d, e
#---------------------------------------------------------------------------------------------------------
### joshwilsdon's test (https://github.com/joyent/node/issues/7161) ###
R[ "joshwilsdon" ] = ->
d1 = [ NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"},
[{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3], {a: "1"}, {a: "1", b: "2"} ]
d2 = [ NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"},
[{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3], {a: "1"}, {a: "1", b: "2"} ]
errors = []
for v1, idx1 in d1
for idx2 in [ idx1 ... d2.length ]
v2 = d2[ idx2 ]
if idx1 == idx2
unless eq v1, v2
errors.push "eq #{rpr v1}, #{rpr v2}"
else
unless ne v1, v2
errors.push "ne #{rpr v1}, #{rpr v2}"
#.......................................................................................................
return [ d1.length, errors, ]
#---------------------------------------------------------------------------------------------------------
return R
`
Like the test cases, the src/implementations.coffee module is of rather light structure:
`coffeescript
#-----------------------------------------------------------------------------------------------------------
module.exports =
# Ex.: how to use JS syntax
#.........................................................................................................
"==: native ==":
#.......................................................................................................
eq: ( a, b ) -> a == ba != b
ne: ( a, b ) ->
# Ex.: how to adapt methods of assertion frameworks
#.........................................................................................................
"NDE: NodeJS assert.deepEqual":
#.......................................................................................................
eq: ( a, b ) ->
try
ASSERT.deepEqual a, b
catch error
return false
return true
#.......................................................................................................
ne: ( a, b ) ->
try
ASSERT.notDeepEqual a, b
catch error
return false
return true
# Ex.: how to adapt other methods
#.........................................................................................................
"LDS: lodash _.isEqual":
#.......................................................................................................
eq: ( a, b ) -> LODASH.isEqual a, b
ne: ( a, b ) -> not LODASH.isEqual a, b
`
The setup is very simple: Each implementation is an object with two members, eq to test for 'is equal to'ne
and to test for 'is not equal to'. Each name starts with a one to three-letter (unique) key which is:
used for reference in the report display (see above), followed by a (colon) and the name proper (which!
should be descriptive and unique). The name may be prepended with an (exclamation sign) in case the wayeq
it has been adapted is suspected to be faulty (happened to me with the QUnit framework, which has a weird
API; i will probably make the relevant test results appear in grey and not include them in the grand
totals). Each function , ne must accept two arguments and return true or false, indicating success
or failure.
There will be a lot of talk about equality and related topics in this text, so it behooves us to shortly if
not strictly define, then at least make sufficiently clear some pertinent terms. Fear not, this formal
discussion will be short, and save for one more stretch of (rather shallow) theoretical discussion, this
ReadMe will remain fairly pragmatic; it may even be said that it is a pronouncedly pragmatic text that aims
to deliver arguments against an ill-conceived standard fraught with artificial rules that serve no practical
purpose (readers who bear with me will not be left in doubt which standard i'm talking about).
Three vocables will have to be used in any discussion of 'what equals what' in programming languages: these
are equality, identity, and equivalence.
First off, to me, **equality and identity are extensional, formal qualities, but equivalence is an
intentional, informal quality**. With 'extensional' i mean 'inherent to the material qualities of a given
value', while 'intentional' pertains to values only in so far as they are put to some specific use, with a
certain functionality or result to be achieved.
Put simply, the physical weight of a given nail is an extensional quality of that nail; that it is used, in
a given case, to hang some framed picture onto some wall is an incidental property of it that arises from a
willful decision of some agent who arranged for this configuration. Likewise, the property that, in
JavaScript, i can say both console.log( 42 ) and console.log( '42' ) to achieve a display of a digit 4console.log( 042 )
is an intentional property; it could be different. It surely strikes us as natural, but that is mainly
because we are so accustomed to write out numbers in the decimal system that we are prone to think of
'number forty-two' as 'sequence of digit 4, digit 2'. This analogy breaks down quickly as soon as one
modifies the setup: when i write (or console.log( 0o42 ) in more recent editions of0
JS), what i get is a sequence of 'digit 3, digit 4', which is different from the sequence , 4, 2 aslog
used in the source that caused this behavior. While it is acceptable to prefer the decimal system for
producing human-readable outputs, JavaScript's handling of strings-that-look-like-numbers is plain nutty.
Consider (using CoffeeScript and for console.log):
`coffeescript`
log '42' + 8 # prints 428
log 42 + '8' # prints 428
log 42 * '8' # prints 336
log '42' * 8 # prints 336
Here, we see that when we use the + (plus operator) to 'add' a string and a number, the output will be a*
string that concatenates the string with the decimal representation of that number. BUT if we use the
(times) operator, we get a number that is the result of the multiplication of the two arguments, the string
being interpreted as a decimal number, where possible. This is so confusing and leads to so many surprising
ramifications that there is, in the community, an expletive to describe such phenomena, and that expletive
is WAT!
I'm discussing these well-known JS WATs in the present context because JavaScript programmers (much like
users of PHP, and certainly more than users of Python) are very much inclined to have a rather muddled
view on data types, and, hence, of equality at large. This is borne out by the refusal of some people
to acknowledge that a method called deepEqual that considers [ 42 ] to 'equal' [ '42' ] is pretty
much useless; more on that topic below.
It can be said that JavaScript's == 'non-strict equals operator' never tested value equality at all,3 == '3'
rather, it tested value equivalence. Now we have seen that equivalence is a highly subjective concept that
is susceptible to the conditions of specific use cases. As such, it is a bad idea to implement it in the
language proper. The very concept that ('number three is equivalent to a string with the ASCIIconsole.log( x )
digit three, U+0033') does hold in some common contexts (like ), but it breaks down inx.length
many other, also very common contexts (like , which is undefined for numbers).
Further, it can be said that JavaScript's === 'strict equals operator' never tested value equality at
all, but rather object identity (alas, with a few lacunae, as we shall see), with the understanding that
all the primitive values have one single identity per value (something that e.g. seems to hold in Python for
all integers, but not necessarily all strings).
An important axiom in computing is that
Axiom 1 Two values x and y can only ever be equal when they both have the same type; conversely,
when two values are equal, they must be of equal type, too.
More formally, let L denote the language under inspection (JS or CS), and be M the meta-language to
discuss and / or to implement L. Then, saying that (in CS) eq x, y results in true implies thateq ( type_of x ), ( type_of y ) must also be true.
We can capture that by saying that in M, all values x of L are represented by tuples ⟨t, v⟩x
where t is the type of and v is its 'magnitude', or call it 'its underlying data proper', i.e. its
'value without its type'—which does sound strange but is technically feasible, since all unique values that
may occur within a real-world program at any given point in time are enumerable and, hence, reducible to
⟨t, n⟩, where n is a natural number. Since all n are of the same type, they can be said to be
typeless.
When we are comparing two values for equality in L, then, we are really comparing the two elements of
two tuples ⟨t1, v1⟩, ⟨t2, v2⟩ that represent the values
in M, and since we have reduced all values to integers, and since types are values, too, we have reduced
the problem of computing eq x, y to doing the equivalent of eq [ 123, 5432, ], [ 887, 81673, ] which hastrue
an obvious solution: the result can only be if the two elements of each tuple are pairwise identical.
> The above is not so abstruse as it may sound; in Python, id( value ) will give you an integer that'foo' === 'foo'
> basically returns a number that represents a memory location, and in JavaScript, types are commonly
> represented as texts. Therefore, finding the ID of a type entails searching through memory whether
> a given string is already on record and where, and if not, to create such a record and return its memory
> address. Further, i would assume that most of the time, maybe always when you do in
> JavaScript, what you really do is comparing IDs, not strings of characters.
>
> To make it very clear: i am not proposing here that the shown implementation of L in M is actually
> used in practical programming languages, or that it would be overall a good design at all; rather, it is
> teaching device like BASIC (intended to be an easy language for beginners) and a thought-experiment like
> Turing machines (intended to make a proof by way of reduction).
I hope this short discussion will have eliminated almost any remaining doubt whether two values of different
types can ever be equal. However, there are two questions i assume the astute reader will be inclined
to ask. These are: what about sub-typed values? and, what about numbers?
As for the first question—what about sub-typed values?—i think we can safely give it short shrift. A
type is a type, irregardless of how it is derived. That an instance of a given type shares methods or data
fields with some other type doesn't change the fact that somewhere it must have—explicitly or implicitly,
accessible from L or only from M—a data field where its type is noted, and if the contents of that
field do not equal the equivalent field of the other instance, they cannot be equal if our above
considerations make any sense. True, some instances of some sub-types may stand in for some instances of
their super-type in some setups, but that is the same as saying that a nail can often do the work of a
screw—in other words, this consideration is about fitness for a purpose a.k.a. equivalence, not about
equality as understood here. Also, that a nail can often do the work of a screw does crucially not hinge on
a screw being conceptualized as 'a nail with a screw thread' or a nail reified as 'a screw with a zero-depth
thread'. In other words, just because, in some languages, both print 3 and print '3' effect the3
appearance of a digit three in the output medium doesn't mean that and '3' are 'the same'.
As for the second question—what about numbers?—it is in theory somewhat harder than the first, but,
fortunately, there is an easy solution.
JavaScript may be said to be simpler than many other languages, since it has only a single numerical
type, which implements the well-known IEEE 754 floating point standard with all its peculiarities.
Many languages do have more than a single numerical type. For instance, Java has no less than six: byte,short, int, long, float, double, which users do have to deal consciously with.
Python before version 3 had four types: int, float, long, complex; in version 3, the int andlong types have been unified. Moreover, Python users have to worry much less about numerical types thanDecimal
Java users, as Python tries very hard—and manages very well—to hide that fact; for most cases, numerical
types are more of a largely hidden implementation detail than a language feature. This even extends to
numerical types that are provided by the Standard Library, like the arbitrary-precision class.
Python has the best thought-out numerical system of any programming language i had ever contact with, so my
rule of thumb is that whatever Python does in the field of numbers is worthy of emulation.
It turns out that in Python, numbers of different types do compare equal when the signs and magnitudes of
their real and complex parts are equal; therefore, 1 == 1.0 == 1 + 0j == Decimal( 1 ) does hold. This
would appear to be in conflict with our theory (since we're comparing values of four different types here),
so either Python gets it wrong or the theory is incorrect.
One way to resolve the conflict is to say that the t in the tuples ⟨t, v⟩ of M do simply record an
abstract type number instead of any subclass of numbers, this being an exception that is made for
practical reasons. Another solution would be to state that our theory is only applicable to languages which
have only a single numerical type, so it may be valid for JavaScript, but certainly not Java or Python.
A third way, and i believe the right one, is to assert that **what Python does with its 1 == 1.0 == 1 + 0j
== Decimal( 1 ) comparison is really not doing equality, but rather equivalence testing, tailored to the==
specific use-case of comparing numerical values for arithmetic purposes**. Indeed, it turns out that Python
allows you to overload the behavior of the operator by defining a special method __eq__ on a class,x == y
and, if you are so inclined, you can make Python say yes to even though x.foo == y.foo does not
hold! It is in fact very simple:
`python
class X:
def __init__( self, foo ):
self.foo = foo
def __eq__( self, other ):
return ( self is not other ) and self.foo % other.foo == 0
x = X( 12 )
y = X( 6 )
z = X( 7 )
print( x == x ) # False
print( x == y ) # True
print( x == z ) # False
print( y == z ) # False
`
This example is more evidence in favor of the above assertion. If Python's == operator had been intended==
to comply with our strict version of equality, there would have been little need to encourage overloading
the operator, as the answer to that question can be given without implementing any class-specificx == x
methods, from an abstract point of view. It is not immediately clear what use could be made of a value that
blatantly violates , but the fact that Python has no qualms in allowing the programmer such utterly
subversive code corroborates the notion that what we're dealing with here is open-minded equivalence rather
than principled equality.
Since there is, anyways, only a single numerical type in JavaScript, i believe we should stick with the
unadultered version of the First Axiom which forbids cross-type equality even for numerical types.
The above treatment of numerical types has shown that Python prefers to consider 1 == 1.0 true because it
may be said that for most practical cases, there will be no difference between results whatever numerical
type you use (although it should be pointed out that already division in older Pythons used to act very
differently whether used with integers or floating-point numbers).
But that, of course, is not quite right; the whole reason for using, say, Decimal instead of Float is
to make it so that arithmetic operations do turn out differently, e.g. in order to deal with precise
monetary amounts and avoid rounding errors (you never calculate prices using floating-point numbers in
JavaScript, right?).
Now, the reason for programmers to write test suites is to ensure that a program behaves the expected way,
and that it continues to return the expected values even when some part of it gets modified. It is clear
that using some BigNum class in place of ordinary numbers will likely make the program change behavior,eq ( new X 0 ), 0
for the better or the worse, and in case you're writing an online shopping software, you want to catch all
those changes, which is tantamount to say you do not want any kind of tests totrue
return , precisely because 0.00 is your naive old way and new X 0.00 is your fool-proof new way of
saying 'zero dollars', and you want to avoid missing out on any regression in this important detail.
Thus our second axiom becomes:
Axiom 2 Even two values x, y of the same type that can be regarded as equal for most use cases, theyeq x, y
must not pass the test in case it can be shown that there is at least one program that has differenty
outputs when run with instead of with x.
The second axiom helps us to see very clearly that Python's concept of equality isn't ours, for there is a
very simple program def f ( x ): print( type( x ) ) that will behave differently for each of 1, 1.0,1 + 0j, Decimal( 1 ). As for JavaScript, the next section will discuss a relevant case.
One of the (many) surprises / gotchas / peculiarities that JavaScript has in store for the n00be programmer
is the existence of two zeroes, one positive and one negative. What, i hear you say, and no sooner said
than done have you typed +0 === -0, return, into the NodeJS REPL, to be rewarded with a satisfyinglytrue
reassuring . That should do it, right?—for haven't we all learned that when a x === y test returnstrue it 'is True', and only when that fails do we have to do more checking? Sadly, this belief is
mistaken, as the below code demonstrates:
`coffeescript
signed_rpr = ( x ) ->
return ( if is_negative_zero x then '-0' else '+0' ) if x is 0
return Number.prototype.toString.call x
is_negative_zero = ( x ) -> x is 0 and 1 / x < 0
test_signed_zero = ->
log +0 == -0 # true
log +1 / +0 # Infinity
log +1 / -0 # -Infinity
log 1 / +0 * 7 # Infinity
log 1 / -0 * 7 # -Infinity
log +0 < 0 # false
log -0 < 0 # false
log +0 * 7 < 0 # false
log -0 * 7 < 0 # false
log Infinity * 0 # NaN
log Infinity / +0 # Infinity
log Infinity / -0 # -Infinity
log signed_rpr +0 ** +1 # +0
log signed_rpr -0 ** +1 # -0
log signed_rpr +0 ** -1 # Infinity
log signed_rpr -0 ** -1 # -Infinity
test_signed_zero()
`
When i first became aware of there being a +0 and a -0 in JS, i immediately wrote a test case: R[ "+0
should eq -0" ] = -> eq +0, -0. I then proceeded adding libraries to jsEq and felt happy that the work iunderscore
put into delivering pretty detailed test reports was not for naught as more and more small differences
between the libraries popped up: this library misses that test case, the next passes the other test, and so
on. I sorted the results, and seeing that got the highscore (pun intended), it surprised me to+0
see that it insisted on treating and -0 as not equal. Ultimately, this led to the discovery of theunderscore
second Axiom, and with that in my hands, it became clear that got this one right and my test
case got it wrong: **Since there are known programs that behave differently with positive and negative zero,
these two values must not be considered equal**.
Yet another one of that rich collection of JavaScript easter eggs (and, like +0 vs -0, one that isNaN
mandated by IEEE 754), is the existence of a (read: Not A Number) value. In my opinion, this valueundefined.x
shouldn't exist at all. JS does consistently the right thing when it throws an exception on undefined
(unable to access property of ) and on f = 42; f 'helo' (f is not a function), and, asInfinity
consistently, fails silently when you access undefined object properties or do numerical nonsense. In the
latter case, it resorts to returning sometimes , and sometimes NaN, both of which make little
sense in most cases.
Now, 'infinity' can be a useful concept in some cases, but there is hardly any use case for NaN, exceptArray( 16 ).join( 'wat' - 1 ) + ' Batman!'
of course for to get, you know that one,
``
NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!
Worse, while NaN is short for 'not a number', typeof NaN returns... 'number'! WAT!
This is not the end to the weirdness: as mandated by the standard, NaN does not equal itself. Now try
and tack attributes unto a NaN, and it will silently fail to accept any named members. There's noNaN
constructor for this singleton value, so you can not produce a copy of it. You cannot delete it from the
language; it is always there, a solitary value with an identity crisis. Throw it into an arithmetic
expression and it will taint all output, turning everything into .
**The sheer existence of NaN in a language that knows how to throw and catch exceptions is an oxymoron, as
all expressions that currently return it should really throw an error instead.**
Having read several discussion threads about the merits and demerits of JS NaN !== NaN, i'm fullyNaN !== NaN
convinced by now that what we have seen concerning Python's concept of numerical equality (which turned out
to be equivalence) applies to as well: it was stipulated because any of a large class ofNaN
arithmetic expressions could have caused a given occurrence of , and claiming that those results are'wat' - 1
'equal' would be tantamount to claiming that equals Infinity * 0, which is obviously wrongInfinity 0 !== Infinity 0
(although it must be said that is not very intuitive, either). Still, NaN
!== NaN is a purpose-oriented stipulation for defining equivalence, not the result of a principled approach
to define strict equality in our sense.
I conclude that according to the First and Second Axioms, eq NaN, NaN must hold, on the grounds
that no program using NaN values from different sources can make a difference on the base of manipulating
these values or passing them as arguments to the same functions.
A collateral result of these considerations is that while JavaScript's === so-called strict equalityNaN
operator (which is really an object identity operator) functions quite well in most cases, it fails with. Specifically, it violates the
Fundamental Axiom: value identity implies value equality. When a given test f purports to test forf x, x
equality, but fails to yield true for any given x, then that test must be considered broken.
Update: i just came across
Object.is,
which, according to MDN,
> determines whether two values are the same value. Two values are the same if one of the following holds:
>
> * both undefined
> * both null
> * both true or both false
> * both strings of the same length with the same characters
> * both the same object
> * both numbers and
> * both +0
> * both -0
> * both NaN
> * or both non-zero and both not NaN and both have the same value
**Evidently, the 'both NaN' and 'both +0' / 'both –0' clauses corroborates our findings in the present and
the previous sections**.
Incidentally, this also shows that regulation 7.1 of
the CommonJS Unit Testing specs is ever so slightly off
the mark when they say:
> All identical values are equivalent, as determined by ===.
(OBS that their use of 'equivalent' doesn't match my definition; the point is that the JS community has
already taken steps to provide for a more precise value identity metric than === can deliver).
Aside: don't use the global function isNaN in your code unless you know what you're doing, as
isNaN is broken.
Instead, do (JS) x !== x (x != xin CS).
Many people in the JS programming community are aware of the issues around ordering of object properties
ever since Chrome (and, because of that, NodeJS) broke customary behavior with regard to the ordering
of object properties. To wit, in NodeJS 0.10.28:
`coffeescript
obj = {}
obj[ 4 ] = 'first'
obj[ 2 ] = 'second'
obj[ 1 ] = 'third'
for name, value of obj
log name, value
`
gives 1 third, 2 second, 4 first, which reflects **the keys re-ordered by their numerical values, not'4'
their order of insertion**. Confusingly, this behavior lingers on when we use , '2', '1' as
keys, and magically vanishes as soon as we use keys that can not be interpreted as (32-bit) integers.
Now, on the one hand it is evident that the ECMA specs do state that objects are unordered collections
of keys and values, but on the other hand, the agreement among browsers has—from the beginning, it would
seem—been that objects should preserve the order of inserted properties. As a commenter
in a relevant thread on esdicuss.org
put it:
> [Object property ordering] behavior was perfectly consistent across all browsers until Chrome 6. I think
> it's more appropriate to say that Chrome is not interoperable with thousands of sites than to define
> interoperable behavior based on a minority browser's very very recent break from a de-facto standard that
> stood for 15 years.
The real problem here lies with the Chrome folks. It is not the only occasion that they completely and
stubbornly shut up when anyone is so impertinent as to criticize their specific readings of their Holy Book.
They surely deserve praise for the general exactness of their work and the swiftness of JavaScript running
inside of V8, but their insistence on even the most marginal of performance gains at the expense of
long-standing but non-standardized expected behavior are nothing short of asinine. Sure, the Specs do not
mandate that any ordering of properties be kept, but does that mean it's a good idea to not keep ordering
when most people expect it, most JS engines keep it, and it can convincingly shown to be a very useful
behavior? But this is idle talk as the Chrome folks will only swear by the millisecond gained in some
synthetic test case. (They are likewise quite indifferent towards the merits of a stable sort and in all
earnesty expect the general public to accept an algorithm that shows one behavior for short and another
behavior for long lists, the threshold being set at an arbitrary limit of ten elements.)
It may then be asked whether our version of strict equality (A) should or (B) should not treat two
objects as equal when their only difference lies in the ordering of properties. First off, there would
appear to be little support from the tested libraries for (B) (i.e. most libraries discard ordering
information). Next, the Specs do not mandate any ordering behavior, so maybe equality tests shouldn't
require it, either. Then again, there is a strawman proposal
so there's a chance a future version of the language will indeed mandate preservation of object key insertion.
Moreover, our Second Axiom makes it quite clear that, since otherwise identical
programs can deliver different outputs for different order of key insertion, and people have come to rely on
consistent ordering for many years, there is something to be said in in favor of solution (B).
I guess that a good pragmatic solution is to go with crowd and use object property ordering
where supported, but not make that factor count in equality tests: **two objects that only differ
in the order of key insertion shall be regarded equal**. Where object key ordering is an important factor,
it can and should be tested separately.
should be tested, also on functions and arrays
should we consider property descriptors? guess that's an opt-in
The difference that exists in many object-oriented languages between primitive values (i.e. values without
properties) and objects (i.e. values that are composed of properties) is puzzling to many people. To make it
clear from the outstart: i believe that wherever and whenever a distinction has been or should have been
made in a program between, say, 5 plain and simple, and new Number 5, then that language is at fault for
not shielding the programmer from such an abomination.
I do get the feeling that the smart people who came up with JavaScript thought along the same lines, and
that the fact that you can sometimes make a difference between 5 and new Number 5 is actually an
oversight where the intention was that programmers should never have to worry about that detail. Thus, in
JavaScript, when you access a property of a primitive value, that primitive is (at least conceptually)
temporarily cast as an object, and suddenly you can access properties on a primitive.
As for our inquiry, we have to ask: should eq 5, new Number 5 hold or not? In the light of the foregoing
discussion, we can give a quick answer: **a primitive value and an equivocal object instance must be
regarded as different.** It follows from our Second Axiom in conjunction with the fact that trying to attach
a property to a primitive or an object will show a different outcome depending on the receiver.
> One could also assert that the need for ne 5, new Number 5 follow from the First Axiom, as 5 andnew Number 5
> are not of the same type. However, it is not quite as clear. After all, checking typesMath.sin()
> can be done in two ways: one way is to submit a given value to a series of tests—how does it behave when
> passed to , what is the result of doing x + '', and so on; the other way is to employtypeof
> JavaScript language constructs like the statement (there's a number of these devices, andtypeof
> usually a judiciously selected combination of several is needed to arrive at a sane result). Now let
> us imagine that all -like devices were not implemented in a language K that compiles totypeof
> JavaScript. Let it further be a known fact that no language construct of K results in an accidental
> use of a -like device in the targetted JavaScript; still, we realize, on perusing the generated5
> JS target code resulting from an input written in K, that in some cases, appears in the targetnew Number 5
> code, and in others. The question is then: can we make it so that a program written inx
> K behaves differently for two values , y, where one compiles to a primitive, the other to anne 5, new Number 5
> object? The answer will be 'yes' in case our probing method (as demonstrated below) can somehow be
> expressed within K. Thus, even in some more restricted dialects of JS, the equivalent of
> should hold; otherwise, our equality testing would be flawed.
>
> The reason i'm going to these lengths here lies in the observation that JavaScript's type system is
> rather deeply broken. It is for this reason that i've written the
> CoffeeNode Types
> package quite a while ago, and the present discussion is reason enough for me to work on a 2.0.0 release
> for that module which will introduce some breaking changes. Long story short: in the absence of a
> clear-cut typing system, using the First Axiom to decide on equality can only be done when type
> difference is more than obvious: a number is not a text, a boolean is not 'null', period. But whether
> or not both a primitive number and an objectified number are of the same type is a much harder question.
In JavaScript, there are the primitive types undefined, null, boolean, string and number;undefined and null are singletons and do not have a constructor, there's only Boolean, NumberString
and . When you try to attach a property to a primitive value, JavaScript will either complainundefined
loudly (in the case of and null), or fail silently (in the case of booleans, numbers and
strings). So one might say that there are really 'primeval primitives' and 'advanced(?) primitives' (rather
than just primitives) in JavaScript.
It gets even a little worse. Consider this:
`coffeescriptvalue == object
test = ( value, object ) ->
value.foo = 42
object.foo = 42
# == === p o
return [ , value === object, value.foo is 42, object.foo is 42 ]
# == === p o
log test 5, new Number 5 # O X X O
log test 'x', new String 'x' # O X X O
log test true, new Boolean true # X X X O
log test /x/, new RegExp /x/ # X X O O
log test [], new Array() # X X O O
`
For readability, i've here rendered true as O and false as X. We can readily discern threeOXXO
patterns of output values, the kind, the XXXO kind, and the XXOO kind. When i say 'kind', i mean
'types of types', and it is plausible that longer series of like tests will result in 'fingerprint patterns'
that will allow us to sort out not only types of types, but also the types themselves.
The sobering factoid that is provided by the above program is that there are at least three kinds of
primitives.
Worse: since NaN is a primitive, too, but singularly fails to satisfy JS x === x, there are at least
four kinds.
Worster still: undefined can be re-defined in plain JS, something you can't do with NaN, so there
are at least five kinds of primitive values in JavaScript.
I think i'll leave it at that.
Undefined properties are quite a nuisance. One might want to think that an 'undefined' property is just a
property that doesn't exit, but in the wonderful world of JavaScript, where there is an undefined value{}[ 'foo' ]
that is actually used as a stand-in return value for cases like and [][ 87 ] (instead of
throwing an exception), that is not so clear. To wit:
`coffeescript
d = { x: undefined }
log Object.keys d # [ 'x' ]
d = [ 'a', 'b', 'c' ]
delete d[ 2 ]
log d.length, Object.keys d # 3 [ '0', '1' ]
d[ 3 ] = undefined
log d.length, Object.keys d # 4 [ '0', '1', '3' ]
`
What this experiment shows is that according whether you base your judgement on (CS) d[ 'x' ] != undefined( ( Object.keys d ).indexOf 'x' ) != 1
or on (CS) , d has or has not a key x. Sometimes the one testundefined
makes sense, sometimes the other; generally, it's probably best to avoid properties whose value has been set
to .
The experiment further shows that delete just 'pokes a hole' into a list (instead of making all subsequentlength
entries move forward one position, as done in Python), but doesn't adjust the property, therefore
causing the same trouble as with other objects (the one thing that can be said in favor of this mode of
operation is that it allows to make sparse lists with arbitrarily large indices on elements).
It may be said without hesitation that ne { x: undefined }, {} should hold without further qualification,
and in fact, there is very broad agreement across implementations about this.
In this section, i want to discuss the tricky question whether two functions f, g can or cannot bef === g
considered equal. First off, it should be clear that whenever (JS) holds, f and g are merelyf
two names for the same object, so they are trivially equal in our sense. The troubles start when and g
are two distinct callables, and this has to do with a couple of topics whose discovery and treatmeant must
be counted among the great intellectual achievements of the 20th century. You know all of these names:
Gödel's incompleteness theorems,
Turing machines,
Halting problem,
Rice's theorem.
I will not iterate any details here, but what the programmer should understand is that **there is no, and
cannot be for logical reasons, any general algorithm that is able to test whether two given programs will
behave equally for all inputs**.
The emphasis is on general, because, of course, there are cases where one can say with confidence that
two given functions behave equally. For example, when i have two f, g that are explicitly limited to a
certain finite set of inputs (say, positive integer numbers less than ten), i can repeatedly call both
functions with each legal input and compare the results. But even that is not strictly true, because it is
simple to define a function that will sometimes deliver a different result (say, based on a random number
generator, or the time of the day). Furthermore, the test will break down when the returned value should be
a function itself, as we are then back to square one then and possibly caught in an infinite regress.
By inspecting source code, there are some cases where a decision can be made more confidently. For example,
if we have
`javascript`
var f = function( a, b, c ){ return a b c; };
var g = function( a, b, c ){ return a b c };
then we can tell with certainty that f and g will return the same value, as the only difference is in the
use of the ; (semicolon) which in JavaScript in this case does not cause any behavioral difference. Thec a b
same goes when i reorder the factors of the product as .
The question is: how to decide whether two functions have only minor differences in their sources? and the
answer is: we shouldn't even try. The reason is simple: A JavaScript program has access to the source
code of (most) functions; as such, we can always inspect that code and cause behavioral differences:
`coffeescript`
log f.toString().indexOf ';' # 38
log g.toString().indexOf ';' # -1
We have now reduced our field of candidates for equality to one remaining special case: how about two
functions for which eq f.toString(), g.toString() holds?
I want to suggest that **two functions may be considered equal when their source code (as returned by
their x.toString() methods) are equal. However, because of some limitations to this, that should be made
optional**.
This i believe should be done for pragmatic reasons as, sometimes, the objects you want to test will contain
functions, and it can be a nuisance to first having to remove them and then be left without any way to
test whether the objects have the expected shapes (i'm not the only one to think so; the generally quite
good equals method by jkroso does essentially the same).
However, there's a hitch here. As i said, JavaScript can access the source of most functions. It cannot
show the source of all functions, because built-ins are typically not written in JS, but compiled (from
C). All that you get to see when you ask for, say, [].toString.toString() will be (at least in NodeJS and
Firefox)
`javascript`
function toString() { [native code] }
and since all objects have that method, it's easy to come up with a litmus test that shows our
considerations are not quite watertight. I use the aformentioned equals implementation here:
`coffeescriptf
eq = require 'equals' # https://github.com/jkroso/equals
f = ( [] ).toString
g = ( 42 ).toString
h = -> 'test method'
log 'does method distinguish functions?', eq ( eq f, g ), ( eq f, h ) # false (1)
log 'are and gequal? ', eq f, g # true (2)`
log 'do they show the same behavior? ', eq ( f.call 88 ), ( g.call 88 ) # false (3)
On line (1), we demonstrate that the eq implementation chosen does indeed considers some functions to beeq f, h
different ( returns false) and others as equal (eq f, g returns true, as the result from linef
(2) shows). According to our reasoning, and g then should show equivalent behaviors for equivalent[object Number]
inputs (in case they are deterministic functions that base their behavior solely on their explicit
arguments, which they are). However, as evidenced by the output of line (3), they return different outputs
(namely and 88) when called with the same argument, 88 (which acts as this in this
case, but that is beside the point).
Actually, i feel a bit stoopid, because, as i'm writing this, another, less contrived, conceptually
simpler, more transparent and probably more relevant counter example comes to my mind, viz.:
`coffeescript
get_function = ( x ) ->
return ( n ) -> n * x
f = get_function 2
g = get_function 3
log eq f, g # true
log eq ( f 18 ), ( g 18 ) # false
`
These functions are the same in the sense that they always execute the same code; they are different in
the way that they see different values in their respective closures. I doubt it will make sense to go much
further than this; to me, the adduced evidence leaves me at 50/50 whether function equivalence makes sense
or not, which is why i think this feature should be made an opt-in.
It is a recurrent feature of many assertion libraries that they provide one method for doing shallow
equality testing and another for deep equality testing. A case in point is NodeJS' assert module with noequal
less than six equality-testing methods: , notEqual, deepEqual, notDeepEqual, strictEqual,notStrictEqual. Given this state of affairs, it is perhaps not so surprising thatdeepStrictEqual
issue #7161: assert.deepEqual doing inadequate comparison
prompted the suggestion to add (and, to keep the tune, notDeepStrictEqual as well)
to the API, which ups the tally to eight.
The reader will not have failed to notice that i make do, in the present discussion and the implementation
of the jsEq package, with a mere two API items, eq and ne, a fourth of what NodeJS offers. One gets==
the impression the CommonJS folks who wrote
the unit testing specs
must have started out, like, "wah equality, that's JS", and then at some point realized "cool, there's JS ===, let's add it, too". A little later someone[] === []
may have pointed out that JS fails, and those folks went, like, "oh noes, we need deepEqual, let's==
add it already". Of course, since they started out with the broken JS operator, they recycled thatdeepEqual
experience when implementing , so now their deepEqual is as broken as their equal. But, hey,
at least its consistently broken, and what's more, we have a standard! Yay!
So now we have a widely-deployed assertion framework which seriously claims that assert.deepEqual [[]], [{}] andassert.deepEqual [ 3 ], [ '3' ] should both hold and not throw exceptions like crazy. One wonders what
the intended use cases for such tests are; i can't think of any.
It looks like it never came to the minds of these folks that JS == is, overall, a pretty much useless===
operator ( was added to JavaScript specifically to remedy the pitfalls of ==; the only reason it did==
not replace was a perceived concern about backwards compatibility). Likewise, it escaped their attention
that APIs do not get better just by adding more and more methods to them.
I believe it can be made unequivocally clear that **separating deep and shallow equality has no place
in an orderly API, especially not in an assertion framework**.
The reasoning is simple: when i test for
equality, i want to test two (or more) values x, y. If i knew for sure that x and y are equal,x
there wasn't a need to test them. Given that i'm unsure about the value of at least one of , y, whichtry ... catch
method—shallow equality for testing primitive values (Booleans, numbers, strings, ...) or deep equality
for testing 'objects' (lists, dates, ...)—should i take? In the absence of more precise knowledge of my
values, i cannot choose. So maybe i do some type checking (notoriously hard to get right in JS), or i
play some games to find out. It is clear that if shallow_equals [], 42false
should fail (or return ) because one of the arguments is not a primitive value, i have to try the other method,deep_equals [], 42. Since the first failed, the second should fail in the same way, so now iequals x, y
know that the two values are not equal according to my library, since i have run out of methods. It is then
easy enough to come up with a method that does exactly that: try one way and, should that fail,true
try the other way, catch all the errors and reduce the output to and false.
There is no reason why the burden of implementing an all-embracing equals method should be put *on theequals
user; rather, it is a failure on part of the library authors to export anything but* an methodnot_equals
(and maybe a method, especially in the context of an assertion library), which is one more reasonassert
**i consider NodeJS' broken: instead of two methods, it exports six (and maybe eight at some point inassert.deepEqual 1234, 1234
the future)**. This is also revealed by the jsEq tests: for instance, assert.notDeepEqual 1234, 1235
and as such work correctly, obviating any need for both assert.equal andassert.notEqual if there ever was one. Their presence is an implementation detail that happened to get
exposed to the general public.
* +1 if method allows to configure whether eq NaN, NaN should hold.eq
* +1 if method allows to configure whether object key ordering should be honored.
* +1 if method allows to configure whether function equality should be tested.
* +1 if method allows to test arbitrary number of arguments for pairwise equality.
* –1 if a (non-assertive) method throws an error on any comparison.
* –1 if a method for deep equality testing fails on primitive values.
* –1 where a method fails on eq x, x for any given x (except for NaN which is a hairy case).eq
* –1 where a library provides both an and a ne method but eq ( not eq x, y ), ( ne x, y ) failsx
for any given and y.x
* –1 where a pair , y can be found that causes eq ( eq x, y ), ( eq y, x ) to fail.String.prototype
* –1000 where anyone dares to pollute the global namespace.
–1000 where anyone dares to monkey-patch built-ins like except* for doing a
well-documented, well-motivated (by existing future standards), well-tested polyfill.
A through comparison of equality-testing implementations whould also consider performance (and maybe memory
consumption). This task has been left for a future day to be written.
* == : native JavaScript comparison with =====
* : native JavaScript comparison with ===OIS
* : native JavaScript comparison Object.isLDS
* : https://github.com/lodash/lodashUDS`: https://github
*