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?
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.)
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.
I was suggested this topic when I was about to start a new development discussion titled “What is a Number”
In general, interfaces and abstract types in Julia are pretty well documented and defined, (e.g. AbstractArray, iterator interface, …). It is suprising to see so little documentation about what it constitutes to be a <:Number, both mathematically and in terms of interface. With currently many active PR’s about making Base code consistent with various number types that are not <:Real or <:Complex, this would probably be good to have.
So first the mathematics, what are the properties of a type in order to be a subtype of Number? Is it supposed to represent elements from a ring , a division ring (I think that this is still fine for Integers, as long as they get promoted to another type under division), a normed division ring, a field (I guess not since non-commutativity of e.g. quaternions is the cause for some recent PRs).
I run into a related problem frequently: if I get a container with element type T <: Number, and create a result S, which I need to precompute, what is the best way to do this? Think of T eg as a ForwardDiff.Dual, or a similar construct related to a tape from one of the reverse AD packages.
Eg if I know I will be using something that can be based on the four basic arithmetic ops (ie a field), I frequently calculate
S = typeof(one(T)/one(T))
If I have multiple inputs,
S = typeof(one(T1)/one(T2)/one(T3))
also seems to work, but always feels like a hack. A fieldclosure(T1, T2, ...) would be nice.
This is the wrong approach I think. I doubt there’s any useful mathematical definition that includes everything that needs to be <: Number, e.g., floating point arithmetic, interval arithmetic, quaternions, dual numbers, etc.
It’s much better to define Number as an interface, and anything that implements the Number interface is acceptable. So it’s about overriding +, -, *, /, etc., not about mathematical consistency.
Yes I was exaggerating, I know it’s not a good idea to try to map mathematical structures onto the type hierarchy for various reasons. But I do mean establishing the kind of mathematical properties that are expected of a Number, i.e. I guess commutative addition operator, associative but not necessarily commutative multiplication operator, is norm part of the required interface, …
Be careful that numbers also include dimensionful quantities, ala Unitful.jl, so ideally generic code should do type computations in a dimensionfully correct way. For example one(T) returns the type of the multiplicative identity for T, which strips away any units.
Writing truly type-generic numeric code, especially handling the dimensionful case, is tricky and takes testing, though.
In my new upcoming Grassmann.jl package, I am providing a generalized number type for which a product algebra is defined. The parametric VectorSpace type system with direct-sum capability helps encode the mathematical properties of each mathematical space, making it possible for many of the computations to be entirely pre-allocated in memory or cached by input. By default, it is defined over a scalar field taking Number types, but I have decided to provide an additional convenient feature which can make extending the entire product algebra to different number types a breeze.
Due to the abstract generality of the code generation of the Grassmann product algebra, it is possible to extend the entire set of operations to other kinds of scalar coefficient types. By default, the coefficients are required to be <:Number . However, if this does not suit your needs, an alternative scalar product algebra can be specified with
where SymField is the desired scalar field and Sym is the scope which contains the scalar field algebra for SymField . Currently, it is feasible to enable symbolic scalar computation, by specifying the operations. It should be straight-forward to substitute any other extended algebraic operations and for extended fields.
My hope is that it will be extensible enough to handle different promotion situations, such as propagating dimensionful quantities, which I have not given much thought to yet.