Honest Equality
JavaScript == is broken. TJS fixes it without breaking anything.
/#
## The Problem with JavaScript ==
JavaScript's `==` does type coercion, producing surprises:
0 == '' // true in JS (!)
false == [] // true in JS (!)
'' == false // true in JS (!)
null == 0 // false in JS (but null == undefined is true)
JavaScript's `===` fixes coercion but can't compare values:
new String('hi') === 'hi' // false in JS (different types)
new Boolean(false) === false // false in JS (object vs primitive)
## TJS Honest Equality (on by default)
`==` becomes **honest equality**: no coercion, but unwraps
boxed primitives. Fast O(1) — no deep comparison.
`Is` / `IsNot` are **structural equality**: deep comparison
for when you explicitly need it. O(n) cost is visible.
Honest equality is on by default in native TJS — no directive needed.
#/
// --- Honest equality (==) fixes coercion ---
console.log('== fixes JS coercion:')
console.log(' [] == ![]:', [] == ![]) // false (JS: true!)
console.log(' 0 == "":', 0 == '') // false (JS: true)
console.log(' false == []:', false == []) // false (JS: true)
console.log(' false == "":', false == '') // false (JS: true)
console.log(' 1 == "1":', 1 == '1') // false (JS: true)
// --- Boxed primitives unwrap ---
console.log('')
console.log('== unwraps boxed primitives:')
console.log(' new String("hi") == "hi":', new String('hi') == 'hi') // true
console.log(' new Boolean(false) == false:', new Boolean(false) == false) // true
console.log(' new Number(42) == 42:', new Number(42) == 42) // true
// --- Nullish equality preserved ---
console.log('')
console.log('Nullish equality (useful pattern preserved):')
console.log(' null == undefined:', null == undefined) // true
console.log(' null == 0:', null == 0) // false
console.log(' null == "":', null == '') // false
// --- Objects/arrays: reference equality (fast, O(1)) ---
console.log('')
console.log('== on objects/arrays is reference equality (fast):')
const obj = {x: 1}
console.log(' obj == obj:', obj == obj) // true (same ref)
console.log(' {x:1} == {x:1}:', {x: 1} == {x: 1}) // false (different refs)
console.log(' [1,2] == [1,2]:', [1, 2] == [1, 2]) // false (different refs)
// --- Is/IsNot: explicit deep structural comparison ---
console.log('')
console.log('Is/IsNot for deep structural comparison (explicit):')
console.log(' {x:1} Is {x:1}:', Is({x: 1}, {x: 1})) // true
console.log(' [1,2,3] Is [1,2,3]:', Is([1,2,3], [1,2,3])) // true
console.log(' [1,2] Is [2,1]:', Is([1,2], [2,1])) // false (order matters)
// Sets compare by membership, not order
console.log(' Set([1,2]) Is Set([2,1]):', Is(new Set([1,2]), new Set([2,1]))) // true
// --- typeof null fixed ---
console.log('')
console.log('typeof null fixed:')
console.log(' typeof null:', typeof null) // 'null' (JS: 'object')
console.log(' typeof undefined:', typeof undefined) // 'undefined'
console.log(' typeof 42:', typeof 42) // 'number' (unchanged)
// --- === unchanged: identity comparison ---
console.log('')
console.log('=== is unchanged (identity):')
console.log(' obj === obj:', obj === obj) // true
console.log(' {x:1} === {x:1}:', {x: 1} === {x: 1}) // false
test 'typeof null is null, not object' {
expect(TypeOf(null)).toBe('null')
expect(TypeOf(undefined)).toBe('undefined')
expect(TypeOf(42)).toBe('number')
expect(TypeOf('hi')).toBe('string')
expect(TypeOf(true)).toBe('boolean')
expect(TypeOf({})).toBe('object')
}
test 'Eq fixes coercion' {
expect(Eq(0, '')).toBe(false)
expect(Eq(false, [])).toBe(false)
expect(Eq('', false)).toBe(false)
}
test 'Eq unwraps boxed primitives' {
expect(Eq(new String('hi'), 'hi')).toBe(true)
expect(Eq(new Boolean(false), false)).toBe(true)
expect(Eq(new Number(42), 42)).toBe(true)
}
test 'Eq preserves nullish equality' {
expect(Eq(null, undefined)).toBe(true)
expect(Eq(null, 0)).toBe(false)
}
test 'Is does deep structural comparison' {
expect(Is({a: 1, b: 2}, {a: 1, b: 2})).toBe(true)
expect(Is([1, 2, 3], [1, 2, 3])).toBe(true)
expect(Is([1, 2], [2, 1])).toBe(false)
}