Interface for `Number`


#1

I recently ran into an issue overriding zero for my own Number:

julia> immutable Infinity <: Number end

julia> Base.zero(::Infinity) = 0

julia> import Base: +

julia> +(::Infinity,::Int) = Infinity()
+ (generic function with 181 methods)

julia> reduce(+,[Infinity(),Infinity()])
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type Infinity
This may have arisen from a call to the constructor Infinity(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] _mapreduce(::Base.#identity, ::Base.#+, ::IndexLinear, ::Array{Infinity,1}) at ./reduce.jl:260
 [2] reduce(::Function, ::Array{Infinity,1}) at ./reduce.jl:321

The issue appears to be that zero{T<:Number}(::T) is expected to return a T.

Is this a bug, or is this part of the Number “Interface”? Is the Number “Interface” written down anywhere?


#2

I’d say it’s part of the interface. If you can’t expect that zero(::T) returns a type T, then lots of codes would be potentially type-unstable.

I don’t think this interface is written down anywhere, but it should get documented better.


#3

OK that’s reasonable. Yes, if built in types have an interface that Base Julia assumes (like for Number) it should be written down.

Even better, there should be a testnumberinterface(Infinity) to test that all the contracts that Base expects are satisfied for my type. (Also, testiterator, testabstractarray, etc.)


#4

Is this new in v0.6?

For v0.5, the following works:

immutable Infinity <: Number end

Base.zero(::Infinity) = 0

import Base: +
+(::Infinity,::Int) = Infinity()
+(::Infinity,::Infinity) = Infinity()

reduce(+,[Infinity(),Infinity()])

#5

Yes it’s 0.6 (sorrry, should have mentioned this was a surprising new behaviour of something that worked fine in 0.5)


#6

Note that the precise problem is not that zero() returns a different type, it’s that the value returned by zero() cannot be converted to Infinity. Defining this (incorrect) method fixes the issue:
Base.convert(::Type{Infinity}, x::Integer) = Infinity().

This sounds like an unintentional restriction to me. It could be worth filing a bug. Ideally what should happen is that either the error would only be thrown when the collection is empty (i.e. when zero must be used), or we would accept a type instability by returning 0 disregarding its type. But these are complex issues.


#7

Created an issue: https://github.com/JuliaLang/julia/issues/21097