Skip to content

Commit 9be25c4

Browse files
committed
types-grammar, ch4: adding discussion of abstract equality (samevalue, stict, and loose/coercive)
1 parent 9fd7300 commit 9be25c4

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

types-grammar/ch4.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,105 @@ ToNumber([]); // 0
222222

223223
By virtue of `ToPrimitive(..,"number")` delegation, these objects all have their default `valueOf()` method (inherited via `[[Prototype]]`) invoked.
224224

225+
### Equality Comparison
226+
227+
When JS needs to determine if two values are the *same value*, it invokes the `SameValue()`[^SameValue] operation, which delegates to a variety of related sub-operations.
228+
229+
This operation is very narrow and strict, and performs no coercion or any other special case exceptions. If two values are *exactly* the same, the result is `true`, otherwise it's `false`:
230+
231+
```
232+
// SameValue() is abstract
233+
234+
SameValue("hello","\x68ello"); // true
235+
SameValue("\u{1F4F1}","\uD83D\uDCF1"); // true
236+
SameValue(42,42); // true
237+
SameValue(NaN,NaN); // true
238+
239+
SameValue("\u00e9","\u0065\u0301"); // false
240+
SameValue(0,-0); // false
241+
SameValue([1,2,3],[1,2,3]); // false
242+
```
243+
244+
A variation of these operations is `SameValueZero()` and its associated sub-operations. The main difference is that these operations treat `0` and `-0` as indistinguishable.
245+
246+
```
247+
// SameValueZero() is abstract
248+
249+
SameValueZero(0,-0); // true
250+
```
251+
252+
If the values are numeric (`number` or `bigint`), `SameValue()` and `SameValueZero()` both delegate to sub-operations of the same names, specialized for each `number` and `bigint` type, respectively.
253+
254+
Otherwise, `SameValueNonNumeric()` is the sub-operation delegated to if the values being compared are both non-numeric:
255+
256+
```
257+
// SameValueNonNumeric() is abstract
258+
259+
SameValueNonNumeric("hello","hello"); // true
260+
261+
SameValueNonNumeric([1,2,3],[1,2,3]); // false
262+
```
263+
264+
#### Higher-Abstracted Equality
265+
266+
Different from `SameValue()` and its variations, the specification also defines two important higher-abstraction abstract equality comparison operations:
267+
268+
* `IsStrictlyEqual()`[^StrictEquality]
269+
* `IsLooselyEqual()`[^LooseEquality]
270+
271+
The `IsStrictlyEqual()` operation immediately returns `false` if the value-types being compared are different.
272+
273+
If the value-types are the same, `IsStrictlyEqual()` delegates to sub-operations for comparing `number` or `bigint` values. You might logically expect these delegated sub-operations to be the aforementioned numeric-specialized `SameValue()` / `SameValueZero()` operations. However, `IsStrictlyEqual()` instead delegates to `Number:equal()`[^NumberEqual] or `BigInt:equal()`[^BigIntEqual].
274+
275+
The difference between `Number:SameValue()` and `Number:equal()` is that the latter defines corner cases for `0` vs `-0` comparison:
276+
277+
```
278+
// all of these are abstract operations
279+
280+
Number:SameValue(0,-0); // false
281+
Number:SameValueZero(0,-0); // true
282+
Number:equal(0,-0); // true
283+
```
284+
285+
These operations also differ in `NaN` vs `NaN` comparison:
286+
287+
```
288+
Number:SameValue(NaN,NaN); // true
289+
Number:equal(NaN,NaN); // false
290+
```
291+
292+
| WARNING: |
293+
| :--- |
294+
| So in other words, despite its name, `IsStrictlyEqual()` is not quite as "strict" as `SameValue()`, in that it *lies* when comparisons of `-0` or `NaN` are involved. |
295+
296+
The `IsLooselyEqual()` operation also inspects the value-types being compared; if they're the same, it immediately delegates to `IsStrictlyEqual()`.
297+
298+
But if the value-types being compared are different, `IsLooselyEqual()` performs a variety of *coercive equality* steps. It's important to note that this algorithm is always trying to reduce the comparison down to where both value-types are the same (and it tends to prefer `number` / `bigint`).
299+
300+
The steps of the *coercive equality* portion of the algorithm can roughly be summarized as follows:
301+
302+
1. If either value is `null` and the other is `undefined`, `IsLooselyEqual()` returns `true`. In other words, this algorithm applies *nullish* equality, in that `null` and `undefined` are coercively equal to each other (and to no other values).
303+
304+
2. If either value is a `number` and the other is a `string`, the `string` value is coerced to a `number` via `ToNumber()`.
305+
306+
3. If either value is a `bigint` and the other is a `string`, the `string` value is coerced to a `bigint` via `StringToBigInt()`.
307+
308+
4. If either value is a `boolean`, it's coerced to a `number`.
309+
310+
5. If either value is a non-primitive (object, etc), it's coerced to a primitive with `ToPrimitive()`; though a *hint* is not explicitly provided, the default behavior will be as if `"number"` was the hint.
311+
312+
Each time a coercion is performed in the above steps, the algorithm is *recursively* reinvoked with the new value(s). That process continues until the types are the same, and then the comparison is delegated to the `IsStrictlyEqual()` operation.
313+
314+
What can we take from this algorithm? First, we see there is a bias toward `number` (or `bigint`) comparison; it nevers coerce values to `string` or `boolean` value-types.
315+
316+
Importantly, we see that both `IsLooselyEqual()` and `IsStrictlyEqual()` are type-sensitive. `IsStrictlyEqual()` immediately bails if the types mismatch, whereas `IsLooselyEqual()` performs the extra work to coerce mismatching value-types to be the same value-types (again, ideally, `number` or `bigint`).
317+
318+
Moreover, if/once the types are the same, both operations are identical -- `IsLooselyEqual()` delegates to `IsStrictlyEqual()`.
319+
320+
### Relational Comparison
321+
322+
// TODO
323+
225324
[^AbstractOperations]: "7.1 Type Conversion", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-type-conversion ; Accessed August 2022
226325

227326
[^ToBoolean]: "7.1.2 ToBoolean(argument)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-toboolean ; Accessed August 2022
@@ -241,3 +340,13 @@ By virtue of `ToPrimitive(..,"number")` delegation, these objects all have their
241340
[^NumberConstructor]: "21.1.1 The Number Constructor", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-number-constructor ; Accessed August 2022
242341

243342
[^NumberFunction]: "21.1.1.1 Number(value)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-number-constructor-number-value ; Accessed August 2022
343+
344+
[^SameValue]: "7.2.11 SameValue(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-samevalue ; Accessed August 2022
345+
346+
[^StrictEquality]: "7.2.16 IsStrictlyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-isstrictlyequal ; Accessed August 2022
347+
348+
[^LooseEquality]: "7.2.15 IsLooselyEqual(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-islooselyequal ; Accessed August 2022
349+
350+
[^NumberEqual]: "6.1.6.1.13 Number:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-number-equal ; Accessed August 2022
351+
352+
[^BigIntEqual]: "6.1.6.2.13 BigInt:equal(x,y)", ECMAScript 2022 Language Specification; https://262.ecma-international.org/13.0/#sec-numeric-types-bigint-equal ; Accessed August 2022

0 commit comments

Comments
 (0)