Irrational numbers are unique, so which package gets to define them?

Irrational numbers are singleton types and are unique, so we have something like

julia> Irrational{:pi}() === Irrational{:pi}()
true

Not many irrational numbers are defined in Base, therefore packages are free to define their own numbers. However the macro Base.@irrational that may be used to define a new irrational number a simultaneously defines Base.Float64(a). This therefore means that a second package that also defines the same number a overwrites the original method definition of Float64(a) with the new – albeit identical – type. For example:

julia> module A
       Base.@irrational a 1.0 float(big(1))
       end
Main.A

julia> module B
       Base.@irrational a 1.0 float(big(1))
       end
Main.B

julia> @which Float64(A.a)
Float64(::Irrational{:a}) in Main.B at irrationals.jl:189

Now this isn’t really a problem for the most part (aside from the unexpected type piracy), since

julia> A.a === B.a
true

and the results would be identical. However when functions are overwritten like this, package precompilation gets broken. One solution appears to be to import A.a inside B, but this means that B needs to depend on A that might otherwise be unrelated. Another solution is to rename the number in B and call it something other than a. However this is undesirable, eg. you wouldn’t name as something else. So which package gets to define the number without precompilation hurdles?

For example, StatsFuns.jl defines quite a few irrationals. How to avoid conflicts with StatsFuns if eg. we want to define sqrt2?

While Base uses a symbol for the type parameter, this is merely a convention. You can define your own type (possibly parametrizing it by a symbol, too) and use that as a parameter. Eg

struct MyIrrational{T} end

Base.Float64(::Irrational{MyIrrational{:sqrt2}}) = √2

From that point on, just avoid type piracy and you will be fine.

4 Likes