How does Julia interpret 10:1?
Asked Answered
I

2

6

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?

Intentional answered 23/3, 2017 at 1:36 Comment(0)
T
14

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.

Thankyou answered 23/3, 2017 at 2:56 Comment(3)
I would put the optimization reason first and foremost -- it's the sine qua non. Thanks for the insight; I had a sneaking suspicion there was an optimization-over-robustness argument in play.Intentional
I don't think that's optimization vs. robustness, really. It's optimization and robustness. Coming from R, I quite like Julia's behavior. In R, when you write complex code where the endpoints come from other computations, you often get bugs because you didn't explicitly handle the case where the starting value is higher than the ending one. An empty range is actually what you want in most of these cases.Thunderstone
@MilanBouchet-Valat fair enough; I can't say I haven't been hit by that type of bug in R.Intentional
F
5

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".

Footstep answered 23/3, 2017 at 2:38 Comment(7)
Is there any justification of this strange behavior? I suppose it's that the object a:b creates is of a certain class that requires a<b? Why not just output a StepRange for 10:1?Intentional
@Intentional the doc clearly says this behavior(a:b<=>a:1:b). Not to mention that, among Matlab, Julia, Python and R, only R interprets 10:1 as 1:10.Footstep
Being new to Julia, I don't know how I could have found the documentation for : -- is there something like ?":" which I use in R? Also, I find the documentation misleading, as a:b is NOT equivalent to a:1:b. a:b creates a UnitRange object; a:1:b creates a StepRange object.Intentional
@Intentional sorry, you're right. It seems there is only a colon function in the doc. I usually use apropos() function to search related docs. (e.g. apropos(1:2) )Footstep
Hmm. apropos(1:2) doesn't return anything about colon for meIntentional
@Intentional then apropos("a:b") works in julia-v0.5, apropos(1:2) works in v0.6.Footstep
or just type "a:b" in help mode ;)Footstep

© 2022 - 2024 — McMap. All rights reserved.