I’m trying to build a type hierarchy around different kinds of units. So far I have this working:
abstract type AbstractUnits{D} end
abstract type AbstractAffineUnits{D} <: AbstractUnits{D} end
abstract type AbstractScalarUnits{D} <: AbstractAffineUnits{D} end
which is quite straightforward. However, I’m trying to tell the compiler that an AbstractDimension{P} is a subtype of AbstractScalarUnits{D} as long as D is a supertype of AbstractDimension{P}. Something like
abstract type AbstractDimensions{P} <: AbstractScalarUnits{AbstractDimensions{P}} end
I was actually surprised that this code ran, but the results are a bit unintuitive:
julia> Dimensions <: AbstractScalarUnits
true #Makes sense
julia> Dimensions <: AbstractScalarUnits{AbstractDimensions}
false #Doesn't make sense
julia> Dimensions{Int} <: AbstractScalarUnits{Dimensions{Int}}
false #Doesn't make sense
Is there some way to make the type system work for this use case? Or do I have to just define an AbstractDimension, and a constant that is a union of “AbstractScalarUnits” and “AbstractDimension”
In Julia syntax, AbstractDimension{P}<:D && AbstractDimension{P}<:AbstractScalarUnits{D}. That’s not what your AbstractDimensions{P} definition describes, and you can’t define a type with multiple supertypes or with a bound that needs the type to already exist.
Explaining this would require you to provide the Dimensions definition, but assuming it’s a straightforward Dimensions{T} <: AbstractDimensions{T}, then Dimensions{Int} <: AbstractScalarUnits{AbstractDimensions{Int}}. Type parameters are only covariant for Tuple, in other words AbstractScalarUnits{Dimensions{Int}} does not subtype AbstractScalarUnits{AbstractDimensions{Int}} and was unrelated to Dimensions{Int}.
I also just realized that what I was attempting to do was to trying to define:
AbstractQuantity{T} <: T
so that
Quantity{<:Number} <: Number
Quantity{<:AbstractArray} <: AbstractArray
It appears that Unitful.jl simply defines
abstract type AbstractQuantity{T,D,U} <: Number end
and leaves it at that, so all AbstractQuantities need to be "Number"s. By contrast, DynamicQuantities defines a whole slew of quantity types, essentially making a parallel hierarchy to tie into Julia’s type system
abstract type AbstractQuantity{T,D} <: Number end
abstract type AbstractGenericQuantity{T,D} end
abstract type AbstractRealQuantity{T,D} <: Real end
struct QuantityArray{T,N,D<:AbstractDimensions,Q<:UnionAbstractQuantity{T,D},V<:AbstractArray{T,N}} <: AbstractArray{Q,N} ....
While this looks horrendous, the upside is that if someone else has algorithms specified to work for <:Number or AbstractArray{<:Number}, these objects would just work as long as you define all the basic functions for `<:Number’. The downside is that you have to define every function for each of these types or use the mother of all type-unions (which risks ambiguities). I wonder if the Holy Traits pattern could help with this.