AbstractVector vs AbstractRange: length vs size -- which is fundamental?

It appears that julia master has changed a behavior from previous versions, and now size for an AbstractRange is defined as (length(r),) instead of the previous behavior where length was defined as prod(size(r)). This makes length a more fundamental function than size, which, fair enough, seems to be the right thing for 1D arrays. However this puts AbstractRanges at odds with AbstractVectors, since AbstractVectors rely on the previous behavior. As an exampe:

julia> struct CustomArray{T,N,A<:AbstractArray{T,N}} <: AbstractArray{T,N}
       parent :: A

julia> Base.size(a::CustomArray) = size(a.parent)

julia> Base.axes(a::CustomArray) = axes(a.parent)

julia> Base.getindex(a::CustomArray{<:Any,N}, i::Vararg{Int,N}) where {N} = getindex(a.parent, i...)

julia> CustomArray(3:4)
2-element CustomArray{Int64, 1, UnitRange{Int64}}:

julia> CustomArray(3:4) |> length

julia> CustomArray(3:4) isa AbstractVector

this works correctly on nightly. Now

julia> struct CustomRange{T,A<:AbstractRange{T}} <: AbstractRange{T}
       parent :: A

julia> Base.size(a::CustomRange) = size(a.parent)

julia> Base.axes(a::CustomRange) = axes(a.parent)

julia> Base.getindex(a::CustomRange, i::Int) where {N} = getindex(a.parent, i)

julia> Base.step(a::CustomRange) = step(a.parent)

julia> CustomRange(3:4)

julia> CustomRange(3:4) |> length
ERROR: length implementation missing

julia> CustomRange(3:4) isa AbstractVector

It’s a bit nonintuitive that for AbstractRanges one needs to define length whereas for AbstractVectors one needs to define size. Although breakages caused by this change may be explained as “missing methods”, perhaps this behavior should be documented better if this is the intent? Should one always define both methods anyway, even though this might be redundant?

1 Like