Difficulty defining `==` for a custom type

I’ve defined two == methods for a custom type, and they work, but I was a bit surprised that:

  1. There is not a symmetry fallback for == as there is for promote_rule
  2. There is not a promotion fallback for ==

The code below is reproducible and contains inline comments showing what results I get at each step. The Tape definition and methods are simplified.

Questions:

  1. Is the expected way to define == for two types just to define two methods ==(::A, ::B) and ==(::B, ::A)?
  2. Are the fallbacks I suggest above actually terrible ideas?

Thanks!

# I want to define a new Vector-ish type and an equality method for it

# Aside: how do I add methods to == without this? I was expecting Base.== to work
import Base: ==

struct Tape{T}
    initial::AbstractVector{T}
    additional::AbstractDict{T,T}
end

Tape(initial::AbstractVector{T}) where T = Tape(initial, Dict{T, T}())

# I want vec == Tape to work
function ==(a::Tape, b::Tape)
    a.initial == b.initial && a.additional == b.additional
end

# These aren't required for later steps, either,
# I was just expecting them to work with a Base fallback
# that doesn't actually exist ;)
Base.convert(::Type{Tape{T}}, v::AbstractVector{T}) where T = Tape(v)
Base.promote_rule(::Type{Tape{T}}, ::Type{<:AbstractVector{T}}) where T = Tape{T}

Tape([1]) == [1] # false
[1] == Tape([1]) # false

# I was expecting promotion and convertion to make this unnecessary
function ==(a::Tape, v::AbstractVector)
    isempty(a.additional) && a.initial == v
end

Tape([1]) == [1] # true
[1] == Tape([1]) # false

# I was expecting a symmetry definition in Base to make this unnecessary
(==)(a::AbstractVector, b::Tape) = b == a

Tape([1]) == [1] # true
[1] == Tape([1]) # true

I would not say that, but I think an advantage of the current approach is that it makes no guesses for the user and allows a lot of fine control.

Instead of ==(x::Any, y::Any) falling back to promotion, it falls back to ===. That is intentional. Also, see eg this example of how not defaulting to promotion is useful.

That said, the part of the docstring that says

New numeric types should implement this function for two arguments of the new type, and handle comparison to other types via promotion rules where possible.

could be explanded and clarified. Please consider making a PR.

Incidentally, you really want to define Base.hash, too.

1 Like