Bug in `reverse!`?

we have

julia> reverse(1:1:2)
2:-1:1

but

julia> a=1:1:2
1:1:2

julia> reverse!(a)
ERROR: setindex! not defined for StepRange{Int64,Int64}
Stacktrace:
 [1] error(::String, ::Type) at ./error.jl:42
 [2] error_if_canonical_setindex(::IndexLinear, ::StepRange{Int64,Int64}, ::Int64) at ./abstractarray.jl:1082
 [3] setindex! at ./abstractarray.jl:1073 [inlined]
 [4] reverse!(::StepRange{Int64,Int64}, ::Int64, ::Int64) at ./array.jl:1524
 [5] reverse!(::StepRange{Int64,Int64}) at ./array.jl:1515
 [6] top-level scope at REPL[49]:1

Ranges are not mutable types, so there’s no in-place reverse! for them:

julia> typeof(1:1:2).mutable
false

If you’re trying to use the same code on both ranges and vectors, you might be interested in BangBang.jl instead, but it doesn’t yet have a reverse!! method. (Maybe someone want’s to write a PR?)

1 Like

See also the lazy Iterators.reverse function.

1 Like

This is a case where you probably don’t need to worry about in-place operations anyway. Ranges are so light (they are like what? 3 numbers?) that it shouldn’t really make a performance difference anyway

2 Likes

Note that constructing a range is not always for free or elided, even when constant propagation would normally apply. range is much more of a hefty operation than you might think:

julia> foo() = range(1, 10, length=30)
foo (generic function with 1 method)

julia> @btime foo()
  116.499 ns (0 allocations: 0 bytes)
1.0:0.3103448275862069:10.0

julia> bar() = 1:0.01:10
bar (generic function with 1 method)

julia> @btime bar()
  166.943 ns (0 allocations: 0 bytes)
1.0:0.01:10.0

Not that this is relevant to the case at hand.

Thank you all. The fact is that I was using the same code for ranges and vectors. Slight uglification to need to special-case ranges.

Yea I assumed that was the case. It is a bit annoying when you run into situations like that.

In this case, I’d just define

reverse!!(r::AbstractRange) = reverse(r)
reverse!!(v::AbstractVector) = reverse!(v) 

and use that.

1 Like

Your answer makes me wonder why Base does not define

reverse!(r::AbstractRange) = reverse(r)

Edit: of course because this does not work, and your solution is also useless:

one has to write

r=reverse!!(r)

with your definition…

Because people might expect long range effects from mutating functions. Consider this:

julia> function foo(xs)
           reverse!!(xs)
           for x in xs
               println(x)
           end
       end

julia> foo(1:5)
1
2
3
4
5

julia> foo(collect(1:10))
5
4
3
2
1

I’m not sure I’d call that useless, but to each their own.

2 Likes

You sort of proved my point by trying to disprove it, but I do applaud the correction. I’m not saying it is free to construct a range, but I doubt that this is something most people need to worry about eliminating from their code. Operations that are non-allocating and with timings on the nanosecond scale are generally not the first place to go looking for performance improvements.

2 Likes