I'm an expat from focusing on R for a long time where the :
(colon) operator creates integer sequences from the first to the second argument:
1:10
# [1] 1 2 3 4 5 6 7 8 9 10
10:1
# [1] 10 9 8 7 6 5 4 3 2 1
Noticing that this appears to work the same in Julia, I ran into an error:
1:10 .== 10:1
DimensionMismatch("arrays could not be broadcast to a common size")
Because:
10:1
Outputs
10:9
I'm puzzled about how this could have happened. It seems quite natural not to need to use 10:-1:1
-- why did Julia think 10:9
was the right interpretation of 10:1
?
Julia is not R. There are other languages which have a similar interpretation for the colon syntax as Julia. MATLAB treats 10:1
as an empty array and Python's slicing syntax (while different in other ways) also treats indexing with 10:1
as an empty selection. Julia chooses to normalize empty integer ranges such that the difference between start and stop is always -1
, so it becomes 10:9
.
So I don't think there's an unambiguously obvious interpretation of 10:1
. There are, however, some very conducive arguments for Julia's interpretation in my view:
The empty range 10:9
is used to represent the location between indices 9 and 10 in some APIs.
Ranges are a core construct in Julia and for x in 1:10
absolutely and unequivocally must be as fast as the equivalent C loop. Because the syntax x:y
always increments by one (and never negative one), Julia (and LLVM) can take advantage of that constant when compiling for loops to enable further optimizations. Making this not constant --- or worse, dynamically switching between UnitRange
and StepRange
depending upon the values of the end points would thwart this optimization or be type-unstable.
Personally, I find R's interpretation just as surprising as you find Julia's. I'd argue that the need to be explicit that you want a step of -1
is advantageous in both readability and bug prevention. But I acknowledge that my experience with prior languages is just as biased as yours is.
In Julia, we assume a:b
constructs a range with a step size of 1, so
10:1
is an UnitRange
which is supposed to be an empty range. Since a:a-1
is also an empty range, it's equivalent to a:b
where b<a
,
please take a look at the source code here.
julia> dump(10:1)
UnitRange{Int64}
start: Int64 10
stop: Int64 9
julia> dump(10:-1:1)
StepRange{Int64,Int64}
start: Int64 10
step: Int64 -1
stop: Int64 1
Here 10:-1:1
is a StepRange
with a step size of -1, which, I think, is more accurate and natural to represent the idea of "10 to 1".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With