Skip to content

Instantly share code, notes, and snippets.

@mheiber
Last active May 31, 2020 10:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mheiber/3f49512afb6d6fb6012bcc45d9b4afbd to your computer and use it in GitHub Desktop.
Save mheiber/3f49512afb6d6fb6012bcc45d9b4afbd to your computer and use it in GitHub Desktop.

Equality: Prior Art in Programming Languages

This document contains notes on how equality works in different programming languages, with a focus on immutable data types in functional languages.

OCaml

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.

Standard ML

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.

Haskell

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 StableNames. Two objects may be Eq equal (deep equal) without having the same StableName.

C++

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#

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

Java

== is value equality for primitives and reference equality for objects.

.equals() is an overrideable method

not 100% sure I got this right

Clojure

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 Maps 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

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment