JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.14k stars 5.44k forks source link

Type-safe equality operators #40717

Open jtrakk opened 3 years ago

jtrakk commented 3 years ago

I often make mistakes by using == between two objects that I shouldn't be comparing, such as comparing a number to an array of numbers or comparing a string to a character. The == comparison evaluates to false, but it wasn't the comparison I meant to make. For example,

a = [1,2,3]
b = 5

if a == b; # evaluates to false

I wish there were an equality-checking operator that would give an error for incompatible types, like in the example above. I'm not sure if the right implementation would be

or something else, or multiple different operators for different purposes. There are plenty of equals-like operators in unicode (eg ≟, ≐). Regardless, the current situation where my mistakes result in silent falses causes me problems too often for comfort; I would much rather get an explicit error that I can correct.

What would be a good solution here?

JeffBezanson commented 3 years ago

The best solution is probably to remove the fallback definition where == calls ===. Then you unambiguously need to use === to check if two things are identical, whatever they might be, and == only when it has been explicitly defined and so "makes sense".

jtrakk commented 3 years ago

For the 1.x version they could use alternative operators like ≟ or ≐ or ~~ or ~~~.

Worth looking at Rust's Eq trait which needs to be explicitly Derived (or explicitly implemented in a custom way) and PartialEq.

mcabbott commented 3 years ago

You can also use isapprox, although perhaps there's no guaranteed it will fail:

julia> a ≈ b
ERROR: MethodError: no method matching isapprox(::Vector{Int64}, ::Int64)
vtjnash commented 3 years ago

I've made that change in a branch before, and in Core.Compiler.:(==), and found we may rely on the fallback definition sometimes more often than you might think. Perhaps those were often supposed to be isequal calls or ===, but it can be hard to be sure everyone has picked the right one.

vtjnash commented 3 years ago

Here's the old code for deprecating the == fallback: https://github.com/JuliaLang/julia/pull/16764/files#diff-12e7a6522633012a408b1bdee7639e8cb722617fe1a8ed6a3881bf4ad1ebdbbdR14

From forensics, it appears that what killed that change in particular is that we define in to be the computation of == over the values in the iterator.

jlapeyre commented 2 years ago

You can also use isapprox, although perhaps there's no guaranteed it will fail:

My package IsApprox does something like this. It encodes different notions of closeness in types. One of these types is Equal.

julia> isapprox(Equal(), 1, 1)
true

julia> isapprox(Equal(), 1, 1 + 1e-15)
false

This is free to be given any symantics, and I think erroring when things can't be sensibly compared is best. But, it currently falls back to ==, which falls back to ===. I can't think of a way to exclude just this last fallback method.

Seelengrab commented 2 years ago

== only when it has been explicitly defined and so "makes sense".

Piggy-backing off of this, should the same be true for hash? That currently falls back to objectid as well, via hash(@nospecialize(x), h::UInt) = hash_uint(3h - objectid(x)).

StefanKarpinski commented 2 years ago

I do really think that it would have been better to make x == y strict and error unless x and y have types that it makes sense to compare and require x === y for the more general "are these the same object" comparison or isequal(x, y) for the "are these hash-equal" comparison.

vtjnash commented 2 years ago

That is how it works inside Core.Compiler

LilithHafner commented 1 year ago

==, ===, isequal, isapprox, , and the entirely unrelated = are already a lot for new folks. Introducing a new which should typically be preferred over == that isn't called == for historical reasons seems fairly beginner unfriendly and equality is something beginners are going to have to work with. I support trying to change the behavior of == in 2.0 but not introducing another notion of equality in 1.x.

jariji commented 9 months ago

While we're at it I don't like this either.

julia> [1; 2; 3;;]
3×1 Matrix{Int64}:
 1
 2
 3
julia> [1; 2; 3;;] ≈ [1,2,3]
true