Correct use of promotion and conversion

I defined the following:

import Base: promote_rule

struct RationalNumber{T<:Integer} <: Real
    num::T
    den::T
end

#RationalNumber{T}(x::S) where {T<:Integer, S<:Integer} = RationalNumber(x, 1) 

RationalNumber(n::T, d::T) where {T<:Integer} = RationalNumber{T}(n,d) 
RationalNumber(n::Integer, d::Integer) = RationalNumber(promote(n,d)...)
RationalNumber(n::Integer) = RationalNumber(n, one(n))
RationalNumber{T}(n::Integer) where {T<:Integer} = convert(T, n) |> RationalNumber

promote_rule(::Type{RationalNumber{T}}, ::Type{S}) where {T,S<:Integer} = RationalNumber{promote_type(T, S)}
promote_rule(::Type{RationalNumber{T}}, ::Type{RationalNumber{S}}) where {T,S<:Integer} = RationalNumber{promote_type(T, S)}

function Base.show(io::IO, r::RationalNumber)
    print(io, "$(r.num)//$(r.den)")
end

I am working on getting comparison to behave properly. Currently, it’s fine when I compare a RationalNumber{T} to another with the same T:

julia> RationalNumber{Int8}(2,3) == RationalNumber{Int8}(2,3)
true

But I get an error when comparing a RationalNumber{T} and RationalNumber{U} where T != U:

julia> RationalNumber(2,3) == RationalNumber{Int8}(2,3)
ERROR: MethodError: no method matching RationalNumber{Int64}(::RationalNumber{Int8})

Closest candidates are:
  (::Type{T})(::T) where T<:Number
   @ Core boot.jl:792
  (::Type{RationalNumber{T}} where T<:Integer)(::Any, ::Any)
   @ Main Untitled-1:848
  (::Type{T})(::AbstractChar) where T<:Union{AbstractChar, Number}
   @ Base char.jl:50
  ...

Stacktrace:
 [1] convert(#unused#::Type{RationalNumber{Int64}}, x::RationalNumber{Int8})
   @ Base ./number.jl:7
 [2] _promote
   @ ./promotion.jl:358 [inlined]
 [3] promote
   @ ./promotion.jl:381 [inlined]
 [4] ==(x::RationalNumber{Int64}, y::RationalNumber{Int8})
   @ Base ./promotion.jl:449
 [5] top-level scope
   @ REPL[4]:1

I expected my second promote_rule method to handle this, but I guess I’m misunderstanding something. Can someone help me properly fix this? I’m inclined to write another constructor which handles the MethodError, but I’m not sure if this is the best way to do it.

You’re just missing a constructor that makes a RationalNumber from another of a different type:

RationalNumber{T}(r::RationalNumber) where {T} = RationalNumber{T}(r.num, r.den)
4 Likes