OffsetArrays, inbounds, and confusion

Several people here and elsewhere have made the same claim that one-based indexing is somehow at fault for the bad interaction between OffsetArrays and code that applies to all AbstractArrays but assumes they all start at a fixed index. This doesn’t really make sense. Let’s imagine a hypothetical world where Julia had used zero-based indexing from the beginning. The same exact history would have happened. We’d start out with a collection of concrete array types in the standard library that are indexed starting at zero. There would be a bunch of code written based on that assumption, including documentation, which people would copy. Then someone would come along and decide that it’s convenient to allow arrays to have arbitrary ranges of indices in different dimensions, and they implement OffsetArrays. At that point you’d have the exact same situation described above, except with zero in place of one. The number one isn’t in anyway significant to the story. Zero-based indexing doesn’t fix anything and one-based indexing isn’t at fault, whatever you may think of it aesthetically.

The other remedy you suggest is to deprecate OffsetArrays and discourage their use. Unlike changing the base of array indexing, this would actually address the issue. But it seems very much like throwing the baby out with the bathwater. Allowing array indices to start at arbitrary offsets is not a wild or controversial idea, nor is it unprecedented—it has a long and successful history in some of the most scientifically minded languages: Fortran, Chapel and Fortress all support it. It works quite well and is frequently useful. Somewhat ironically, given your username, one of the use cases for offset arrays is when working with Fourier transforms, where it allows indices to be symmetrical around zero. There are many examples where high-level array code gets much clearer if you can choose convenient index ranges and let the array do the offset bookkeeping for you. The Images documentation has a really nice example here.

Perl also allows changing the indexing base for arrays, but strongly discourages anyone from ever doing this. In recent versions it’s even disallowed to assign anything but zero to $[, the very Perlishly named variable that controls the base index. This is often trotted out as an object lesson in how obviously bad an idea it is to allow array indexing to start at different locations. But the problem in Perl is that $[ is a global flag that affects all arrays everywhere. So no code anywhere can assume any particular starting index, even for arrays that were constructed locally. In effect, this feature just changes the correct syntax for indexing into an array from $a[i] to $a[$[+i]. (Yes, that second expression is valid Perl syntax, unbalanced brackets and all. Extremely cursed.) This feature is not only confusing but also completely useless. Looking at this Perl misfeature and concluding that letting array indexing start at different offsets is a terrible idea is like eating some milk that you forgot in your fridge for a few months and concluding that cheese is a terrible idea.

Back to Julia: the right solution is to fix code that accepts AbstractArray but does things like 1:length(a). All you need to do is replace that range with eachindex(a). Then the code works with any starting index, including one or zero. The language already has all the features and APIs needed to write fast, generic that works with all kinds of arrays. We just need to educate and encourage people to uses them. This particular transformation can be done almost automatically. Linter checks suggesting this fix have already been implemented, to help educate people. Many languages have patterns that should be avoided and help people avoid them in this exact way—it’s basically why IDE linter suggestions were invented.

38 Likes