juliainfix-operator

Julia manual and defining an infix operator


The following code is from the Julia manual. My question is below.

julia> struct OurRational{T<:Integer} <: Real
           num::T
           den::T
           function OurRational{T}(num::T, den::T) where T<:Integer
               if num == 0 && den == 0
                    error("invalid rational: 0//0")
               end
               num = flipsign(num, den)
               den = flipsign(den, den)
               g = gcd(num, den)
               num = div(num, g)
               den = div(den, g)
               new(num, den)
           end
       end

julia> OurRational(n::T, d::T) where {T<:Integer} = OurRational{T}(n,d)
OurRational

julia> OurRational(n::Integer, d::Integer) = OurRational(promote(n,d)...)
OurRational

julia> OurRational(n::Integer) = OurRational(n,one(n))
OurRational

julia> ⊘(n::Integer, d::Integer) = OurRational(n,d)
⊘ (generic function with 1 method)

julia> ⊘(x::OurRational, y::Integer) = x.num ⊘ (x.den*y)
⊘ (generic function with 2 methods)

julia> ⊘(x::Integer, y::OurRational) = (x*y.den) ⊘ y.num
⊘ (generic function with 3 methods)

julia> ⊘(x::Complex, y::Real) = complex(real(x) ⊘ y, imag(x) ⊘ y)
⊘ (generic function with 4 methods)

julia> ⊘(x::Real, y::Complex) = (x*y') ⊘ real(y*y')
⊘ (generic function with 5 methods)

julia> function ⊘(x::Complex, y::Complex)
           xy = x*y'
           yy = real(y*y')
           complex(real(xy) ⊘ yy, imag(xy) ⊘ yy)
       end
⊘ (generic function with 6 methods)

As a consequence of those definitions the manual gives this example

julia> z = (1+2im)⊘(1-2im);
Julia> typeof(z)
Complex{OurRational{Int64}}

Which works fine ... unless you leave off the ';'

julia> z = (1+2im)⊘(1-2im)
OurRational{Int64}(-3, 5)Error showing value of type Complex{OurRational{Int64}}:
ERROR: promotion of types OurRational{Int64} and Int64 failed to change any arguments
Stacktrace:
  [1] error(::String, ::String, ::String)
    @ Base ./error.jl:44
  [2] sametype_error(input::Tuple{OurRational{Int64}, Int64})
    @ Base ./promotion.jl:417
  [3] not_sametype(x::Tuple{OurRational{Int64}, Int64}, y::Tuple{OurRational{Int64}, Int64})
    @ Base ./promotion.jl:411
  [4] promote
    @ ./promotion.jl:394 [inlined]
  [5] <(x::OurRational{Int64}, y::Int64)
    @ Base ./promotion.jl:462
  [6] signbit(x::OurRational{Int64})

I've no idea why. If you use the builtin Rational operator

julia> z = (1+2im)//(1-2im)

No problems.

Any idea?


Solution

  • The underlying issue is that OurRational{Int} and Int are incompatible types: an Int cannot be converted into an OurRational{Int}. Inspecting the stack trace will show this:

    OurRational{Int64}(-3, 5)ERROR: promotion of types OurRational{Int64} and Int64 failed to change any arguments
    Stacktrace:
      [1] error(::String, ::String, ::String)
        @ Base ./error.jl:44
      [2] sametype_error(input::Tuple{OurRational{Int64}, Int64})
        @ Base ./promotion.jl:417
      [3] not_sametype(x::Tuple{OurRational{Int64}, Int64}, y::Tuple{OurRational{Int64}, Int64})
        @ Base ./promotion.jl:411
      [4] promote  <-- issue here ********
        @ ./promotion.jl:394 [inlined]
      [5] <(x::OurRational{Int64}, y::Int64)
        @ Base ./promotion.jl:462
      [6] signbit(x::OurRational{Int64})
        @ Base ./number.jl:137
      [7] show(io::IOContext{Base.TTY}, z::Complex{OurRational{Int64}})
        @ Base ./complex.jl:201
      [8] show(io::IOContext{Base.TTY}, ::MIME{Symbol("text/plain")}, x::Complex{OurRational{Int64}})
        @ Base.Multimedia ./multimedia.jl:47
    <snip>
    

    To fix this, define:

    Base.promote_rule(::Type{OurRational{T}}, ::Type{S}) where {T,S} =
        OurRational{promote_type(T, S)}
    

    And now…

    OurRational{Int64}(-3, 5)ERROR: MethodError: no method matching OurRational{Int64}(::Int64)
    
    Closest candidates are:
      OurRational{T}(::T, ::T) where T<:Integer
       @ Main ~/Offline-Documents/Scratchpad/f.jl:5
      (::Type{T})(::T) where T<:Number
       @ Core boot.jl:792
      (::Type{T})(::Complex) where T<:Real
       @ Base complex.jl:44
      ...
    
    Stacktrace:
    <snip>
    

    Ok, let's define that then:

    OurRational{T}(n::T) where {T<:Integer} = OurRational(n)
    

    Not quite there…

    OurRational{Int64}(-3, 5)ERROR: < not defined for OurRational{Int64}
    Stacktrace:
    <snip>
    

    One more try:

    # actual implementation left as exercise
    Base.:(<)(x::OurRational{S}, y::OurRational{T}) where {S,T} = x.num//x.den < y.num//y.den
    

    Success!

    julia> z = (1 + 2im) ⊘ (1 - 2im)
    OurRational{Int64}(-3, 5) + OurRational{Int64}(4, 5)*im