Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type-safe equality operators #40717

Open
jtrakk opened this issue May 5, 2021 · 10 comments
Open

Type-safe equality operators #40717

jtrakk opened this issue May 5, 2021 · 10 comments
Labels
breaking This change will break code equality Issues relating to equality relations: ==, ===, isequal
Milestone

Comments

@jtrakk
Copy link

jtrakk commented May 5, 2021

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

  • check if they have exactly the same type
  • check some <: subtyping relationship
  • check if they can convert into each other's types
  • check if they can promote into a shared type

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 JeffBezanson added this to the 2.0 milestone May 5, 2021
@JeffBezanson JeffBezanson added the breaking This change will break code label May 5, 2021
@JeffBezanson
Copy link
Member

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
Copy link
Author

jtrakk commented May 5, 2021

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
Copy link
Contributor

mcabbott commented May 6, 2021

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
Copy link
Member

vtjnash commented May 6, 2021

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
Copy link
Member

vtjnash commented May 6, 2021

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.

@vtjnash vtjnash added the equality Issues relating to equality relations: ==, ===, isequal label May 10, 2021
@jlapeyre
Copy link
Contributor

jlapeyre commented Nov 6, 2021

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
Copy link
Contributor

== 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
Copy link
Member

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
Copy link
Member

vtjnash commented Mar 31, 2022

That is how it works inside Core.Compiler

@LilithHafner
Copy link
Member

==, ===, 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking This change will break code equality Issues relating to equality relations: ==, ===, isequal
Projects
None yet
Development

No branches or pull requests

8 participants