This document contains notes on how equality works in different programming languages, with a focus on immutable data types in functional languages.
OCaml and Reason
OCaml has two equality primitives:
- = is for structural equality
- == is for 'physical' (reference) equality
Using =
triggers an exception when used on two things that don't have meaningful structural equality in OCaml (such as functions).
Reason is OCaml with different syntax.
SML has one equality primitive which behaves differently for value types and reference types, respectively. For example, tuples and records are tested using structural equality; and mutable built-ins like reference cells are tested using reference or pointer equality.
For user-defined types:
- If the user-defined type recursively only contains immutable values,
=
is structural - If the user-defined type contains any mutable values,
=
throws.
Equality is pluggable in Haskell: users can provide instances for the Eq
typeclass.
Haskell authors typically use deriving (Eq)
in a data
definition so that Haskell automatically generates an Eq
instance. These instances seem to be based on deep equality.
The closest thing to pointer equality in Haskell that I could find is that users can immitate pointer equality by using StableName
s. Two objects may be Eq
equal (deep equal) without having the same StableName
.
classes can define their own ==
.
std::adressof(thing)
is pointer equality. This was added because &
is also overrideable.
not 100% sure I got this right
C# ==
means value equality for primitives and (usually) reference equality for everything else. However, its behavior is overrideable.
System.Object.ReferenceEquals(a, b)
is always reference equality (same object in memory) and returns false
for primitives.
not 100% sure I got this right
==
is value equality for primitives and reference equality for objects.
.equals()
is an overrideable method
not 100% sure I got this right
Clojure provides =
(structural equality), identical?
(pointer equality) and ==
(numerical value equality with implicit casting).
=
is structural equality for immutable values.
For Java interop, for certain specified Java collections (List
, Set
, Map
), =
is pointwise comparison of the items in the collections. For example, two Map
s are =
if they have the same (=
) keys and the same (=
) value for each key.
=
has several edge cases and surprises.
For mutable Clojure objects, =
falls back to identical?
For numbers ==
is like =
but also performs implicit casts between numerical types.
Python provides is
for 'identity' and ==
for 'equality'. is
is pointer equality. ==
in practice means "equality of whatever is returned by the __hash__()
method".
(1, 2, 3) == (1, 2, 3) # True
(1, [], 3) == (1, [], 3) # False
By default, equality of instances of user-defined classes is based on reference.
class A:
pass
a = A()
a == A() # False
(A(),) == (A(),) # False
(a,) == (a,) # True
There is a decorator to make a user-defined class immutable, which also makes the equality structural:
@dataclass
class A:
pass
a = A()
a == A() # True
(A(),) == (A(),) # True
(a,) == (a,) # True