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