Type promotion not working as expected

So I am learning Julia from Erik Engheim’s book “Julia for Beginners”. I am working through the code as I read the book (I put all my work in Github if anyone else is using the book and want to compare their own code)

He has a chapter on defining angle types as a way to teach type conversion rules and promotion (which is quite a nice feature of Julia). Unfortunately, my code isn’t working as expected and I’m not sure why. Here is the code:

abstract type Angle end

struct Radian <: Angle

struct DMS <: Angle

    Degree(degrees::Integer) = Minute(degrees * 60)
    Degree(deg::Integer, min::Integer) = Degree(deg) + Minute(min)
    Degree(deg::Integer, min::Integer, secs::Integer) = Degree(deg, min) + Second(secs)

    function Minute(minutes::Integer)
        DMS(minutes * 60)

    function Second(seconds::Integer)

    import Base: -, +, show, convert, *, /, promote_rule
    +(Θ::DMS, α::DMS) = DMS(Θ.seconds + α.seconds)      
    -(Θ::DMS, α::DMS) = DMS(Θ.seconds - α.seconds)

    +(Θ::Radian, α::Radian) = Radian(Θ.radians + α.radians)
    -(Θ::Radian, α::Radian) = Radian(Θ.radians - α.radians)

    function degrees(dms::DMS)
        minutes = dms.seconds ÷ 60
        minutes ÷ 60

    function minutes(dms::DMS)
        minutes = dms.seconds ÷ 60
        minutes % 60

    seconds(dms::DMS) = dms.seconds % 60

    function show(io::IO, dms::DMS)
    print(io, degrees(dms), "° ", minutes(dms), "' ", seconds(dms), "''")

    function show(io::IO, rad::Radian)
    print(io, rad.radians, "rad")

    # convert constructors
    Radian(dms::DMS) = Radian(deg2rad(dms.seconds/3600))
    DMS(rad::Radian) = DMS(floor(Int, rad2deg(rad.radians) * 3600))

    convert(::Type{Radian},  dms::DMS)    = Radian(dms)
    convert(::Type{DMS},     rad::Radian) = DMS(rad)

    sin(rad::Radian) = Base.sin(rad.radians)
    cos(rad::Radian) = Base.cos(rad.radians)

    sin(dms::DMS) = sin(Radian(dms))
    cos(dms::DMS) = cos(Radian(dms))

    *(coeff::Number, dms::DMS) = DMS(coeff * dms.seconds)
    *(dms::DMS, coeff::Number) = coeff * dms
    /(dms::DMS, denom::Number) = DMS(dms.seconds/denom)

    *(coeff::Number, rad::Radian) = Radian(coeff * rad.radians)
    *(rad::Radian, coeff::Number) = coeff * rad
    /(rad::Radian, denom::Number) = Radian(rad.radians/denom)

    const ° = Degree(1)
    const rad = Radian(1)

    Base.promote_rule(::Type{Radian}, ::Type{DMS}) = Radian

As can be seen I have a promote rule and this works for me:

# result: 

But it I just do a + without the explicit promote it complains. I don’t understand why it isn’t automatically promoting the DMS to Radian:

90°+ 3.14rad/2
# result:
ERROR: MethodError: no method matching +(::DMS, ::Radian)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
  +(::DMS, ::DMS) at /Users/aronet/Code/FourM/Study/Julia/JuliaforBeginners/angleunits.jl:25
  +(::Radian, ::Radian) at /Users/aronet/Code/FourM/Study/Julia/JuliaforBeginners/angleunits.jl:28
 [1] top-level scope
   @ REPL[5]:1

Thanks for any help.

abstract type Angle <: Number end


Alternatively, define methods

+(x::Angle, y::Angle) = +(promote(x,y)...)
# etc.

Number already has these defined, so Angle inherits them.

if the point is to get promotion rules to do the work for you, this would be going in the other direction…

Yes, either inherit them, or do it yourself.

@jling Thanks so much! That solved the problem!

For those reading the book, I am not sure why Engheim left this out in his definition but it seems he might have preferred @DNF’s solution since he is trying to get you to “do the work yourself”. Of course he left that bit out, which was confusing. Thanks @DNF for your insights as well.

That makes sense. One thing to note is that although Julia is flexible enough to let you write your own promote rules, doing so correctly is surprisingly difficult, and result in infinite recursion. As such for most real use cases, you probably want to just use the ones in Base since they probably do what you want and are relatively reliable.


my advise is: if they are really number-like thing, user promotion to make life easy, other-wise, don’t. For example, you should never use promotion rule for adding vector to a scalar.