Julian way of defining custom numeric types

Hello,

I am working with the DifferentialEquations package to play around some differential equations involving angles.

Instead of letting everything go on as a normal float and just afterwards reduce everything modulo 2pi for visualisation, i wanted to try to go the julian way and define a small wrapper for the Floag64 type

struct Angle <: Real
    x::Float64
end
Angle(x::Real) = Angle(x % 2pi)

Unfortunately this is not enough to make for a smooth addition to my program as julia struggles to convert and does not know what to do with this new type.

I know that i can extend all the base operations by hand, but i was left wondering if there is a smoother and easier way to let a simple primitive Number type wrapper just automatically fall in place the rest of the Julia implementation.

More in general i tried to look for some quick guidelines when creating a new custom type but i could not find one. Are there any Best Practices ™ when it comes to seamlessly embed a new custom type in the language?

Thanks!

You should find an inspiration in manual describing the implementation of rational number.

https://docs.julialang.org/en/v1/manual/conversion-and-promotion/

Also, I think you can benefit from the macro @forward that I describe in this reply of mine.

This is extremely interesting but the @forward macro seems to not retain the type as in

struct Angle <: Real
    x::Float64
end

@forward Angle.x Base.sqrt

then

typeof(Base.sqrt(Angle(1))) == Float64 #true

and that is not really what i need. Moreover, using forward on Base.:+ raises a MethodError anyway as there is ambiguity as the macro defines

+(x::Angle, args...; kwargs...)

I think i’ll stick the the “not lazy” version of embedding a custom type for now.

True, maybe eval would be more the case here.

for single_param_function in (:(Base.sqrt), :(Base.sin))
    eval(quote
        function $(single_param_function)(a :: Angle)
            return Angle($(single_param_function)(a.x))
        end
    end)
end

Someone with better knowledge of metaprogramming should check if this is reasonable (it works, but maybe is not ideal).

Note that you will get more accurate results by using mod2pi(x) instead of x % 2pi (since the latter uses 2pi, a Float64 approximation of the actual number 2Ď€).

Is the sine of an angle another angle or just a number? If you don’t need it to be an angle, then simply defining Base.float for your type will work for a lot of functions that use this as their fallback.

julia> Base.float(a::Angle) = a.x

julia> sin(Angle(1))
0.8414709848078965