Julia: `LinRange{Int}(...)` allows creating a LinRange that doesn't really work

Created on 22 Mar 2020  ·  7Comments  ·  Source: JuliaLang/julia

eltype(LinRange{Int}(1, 4, 5)) is Int, whereas the elements are actually of type Float64, since LinRange uses / for division. Furthermore, LinRange{Int}(1, 4, 5) isa AbstractRange{Int} is also technically wrong, since the docstring of AbstractRange clearly states:

  AbstractRange{T}

  Supertype for ranges with elements of type T. UnitRange and other types are subtypes of this.

Thanks to @asinghvi17 for bringing this up on Slack!

arrays bug help wanted

Most helpful comment

In addition to improving the printing, one could also throw an error when creating a LinRange{<:Integer} that would contain non-integer elements. I have a PR (#32439) that does this for StepRange{<:Integer} and StepRangeLen{<:Integer} and will update it to include LinRange.

All 7 comments

This is probably not fixable with the current type system. The issue is you need

julia> struct MyLinRange{T} <: AbstractRange{typeof(zero(T)/1)}
           start::T
           stop::T
           len::Int
           lendiv::Int
       end
ERROR: MethodError: no method matching zero(::TypeVar)
Closest candidates are:
  zero(::Type{Missing}) at missing.jl:103
  zero(::Type{LibGit2.GitHash}) at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.4/LibGit2/src/oid.jl:220
  zero(::Type{Pkg.Resolve.VersionWeight}) at /home/tim/src/julia-1/usr/share/julia/stdlib/v1.4/Pkg/src/Resolve/versionweights.jl:15
  ...
Stacktrace:
 [1] top-level scope at REPL[1]:1

Alternatives include adding a second type parameter,

julia> struct MyLinRange{Item,T} <: AbstractRange{T}
           start::Item
           stop::Item
           len::Int
           lendiv::Int

           function MyLinRange{Item,T}(start,stop,len) where {Item,T}
               len >= 0 || throw(ArgumentError("range($start, stop=$stop, length=$len): negative length"))
               if len == 1
                   start == stop || throw(ArgumentError("range($start, stop=$stop, length=$len): endpoints differ"))
                   return new(start, stop, 1, 1)
               end
               new(start,stop,len,max(len-1,1))
           end
       end

julia> MyLinRange{Item}(start, stop, len) where Item = MyLinRange{Item,typeof(start/1)}(start, stop, len)

but I am not sure whether this would be regarded as too breaking.

Note that this issue doesn't come up with LinRange{1, 3, 3) (i.e., if you don't specify the type parameter).

The elements of LinRange{Int} are actually Int. The printing is misleading.

julia> l = LinRange{Int}(1,5,4)
4-element LinRange{Int64}:
 1.0,2.33333,3.66667,5.0

julia> l[1]
1

julia> l[end]
5

julia> l[2]
ERROR: InexactError: Int64(2.333333333333333)
Stacktrace:
 [1] Int64 at ./float.jl:710 [inlined]
 [2] lerpi at ./range.jl:687 [inlined]
 [3] unsafe_getindex at ./range.jl:681 [inlined]
 [4] getindex(::LinRange{Int64}, ::Int64) at ./range.jl:666
 [5] top-level scope at REPL[12]:1

Oh, right, I had forgotten about that. That's a relief. So we should just fix the printing.

In addition to improving the printing, one could also throw an error when creating a LinRange{<:Integer} that would contain non-integer elements. I have a PR (#32439) that does this for StepRange{<:Integer} and StepRangeLen{<:Integer} and will update it to include LinRange.

Alternatives include adding a second type parameter,

Yes, I think that would be the right way to do it. (The type parameter can be set by a constructor, and invalid constructor calls can be errors.)

The misleading printing is fixed by #35267.

Triage thinks this is fixed now. If you explicitly ask for Int elements, you should indeed get an InexactError if the element values can't be converted to that.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

musm picture musm  ·  3Comments

Keno picture Keno  ·  3Comments

helgee picture helgee  ·  3Comments

tkoolen picture tkoolen  ·  3Comments

StefanKarpinski picture StefanKarpinski  ·  3Comments