Currently, the docstring for AbstractUnitRange
states:
AbstractUnitRange{T} <: OrdinalRange{T, T}
Supertype for ranges with a step size of `oneunit(T)` with elements of type `T`.
This implies that the step
must be of type T
as well. However, this is not enforced anywhere, and as a consequence, one may create ranges for which this is not obeyed at all. Even the test suite for ranges in Base
has some examples like this:
struct Position <: Integer
val::Int
end
Position(x::Position) = x # to resolve ambiguity with boot.jl:770
struct Displacement <: Integer
val::Int
end
Displacement(x::Displacement) = x # to resolve ambiguity with boot.jl:770
Base.:-(x::Displacement) = Displacement(-x.val)
Base.:-(x::Position, y::Position) = Displacement(x.val - y.val)
Base.:-(x::Position, y::Displacement) = Position(x.val - y.val)
Base.:-(x::Displacement, y::Displacement) = Displacement(x.val - y.val)
Base.:+(x::Position, y::Displacement) = Position(x.val + y.val)
Base.:+(x::Displacement, y::Displacement) = Displacement(x.val + y.val)
Base.:(<=)(x::Position, y::Position) = x.val <= y.val
Base.:(<)(x::Position, y::Position) = x.val < y.val
Base.:(<)(x::Displacement, y::Displacement) = x.val < y.val
# for StepRange computation:
Base.Unsigned(x::Displacement) = Unsigned(x.val)
Base.rem(x::Displacement, y::Displacement) = Displacement(rem(x.val, y.val))
Base.div(x::Displacement, y::Displacement) = Displacement(div(x.val, y.val))
# required for collect (summing lengths); alternatively, should length return Int by default?
Base.promote_rule(::Type{Displacement}, ::Type{Int}) = Int
Base.convert(::Type{Int}, x::Displacement) = x.val
Base.Int(x::Displacement) = x.val
With this definition, one may define:
julia> r = Position(2):Position(5)
Position(2):Position(5)
julia> r isa AbstractUnitRange{Position}
true
julia> step(r)
Displacement(1)
julia> step(r) |> typeof
Displacement
This range is, strictly speaking, a StepRange
, with a unit step. We don’t have a UnitStepRange
type in Base
, which leads to the abuse of UnitRange
s. However, disregarding type-parameters in this manner has the potential to open up unexpected bugs. Is it reasonable to disallow such non-compliant AbstractUnitRange
s? I understand that there are concerns about the performances of StepRange
s, but perhaps we may add a UnitStepRange
type to get around these?
As an example, Dates
already avoids this issue in the colon range constructor, and returns a StepRange
instead:
julia> today():today()
Date("2023-07-17"):Day(1):Date("2023-07-17")
The docstring for Colon
states that it produces
a `UnitRange` when a and b are integers,
but that may be altered. IMO this should not produce a UnitRange
if typeof(a-b) != typeof(a)