Add operator support for mathematical equivalence

I think that there isn’t really a single notion of equality but there is only one == operator so people have used it for different things. Even without pathological examples == is a mix of structural equality and mathematical equivalence:

In [1]: [1, 2] == (1, 2)
Out[1]: False

In [2]: [1.0, 2] == [1, 2]
Out[2]: True

A general mathematical equivalence operator is impossible to define or implement but structural equality is something that can be given a well defined meaning and has many uses.

When implementing a type that defines __eq__ you choose between conflicting things:

  • Implementing a notion of mathematical equivalence that people often expect based on things like 1 == 1.0.
  • Implementing a structural equality that is useful for things like hash tables.
  • Making something that works like a DSL as in the case of numpy arrays.
  • Equality based on identity as is the case for object.__eq__.

These can all be conflicting in different situations. The example of symbolic expressions was given above where users very often want == to mean mathematical equivalence but the standard notion of equivalence for symbolic expressions is algorithmically undecidable. Structural equivalence is what is needed if you want to be able to use expressions in dicts, sets etc but is surprising to people expecting some kind of mathematical equivalence. For a DSL-like version you would want to be able to return a different type as numpy does e.g. this is with sympy:

In [3]: sin(x) > 0 # a Mathematical proposition
Out[3]: sin(x) > 0

In [4]: sin(x) == 0 # structural equality
Out[4]: False

In [5]: Eq(sin(x), 0) # people want sin(x) == 0 to do this
Out[5]: sin(x) = 0

In [6]: solveset(Eq(sin(x), 0), x)
Out[6]: {2⋅n⋅π │ n ∊ ℤ} ∪ {2⋅n⋅π + π │ n ∊ ℤ}

In [7]: solveset(sin(x) == 0, x) # a very common trap
Out[7]: ∅

I don’t think that adding __equiv__ or ~ would lead to a useful general concept of equivalence any more so than what we already have with ==. The only value that binary ~ could provide is just another binary operator that people can use in DSLs (an idea that has been suggested in the past).

What would be useful is being able to separate structural equality comparison from any sense of mathematical equivalence. Then you can have an operator with a more well-defined and useful general meaning that does not need to compromise with anyone’s mathematical sensibilities about whether something like 1 == 1.0 must be true or what nans should do or any legacy of __eq__ returning types other than bool.

3 Likes