Skip to content

Commit bff597f

Browse files
committed
Update devdocs for offset arrays
1 parent f036000 commit bff597f

File tree

1 file changed

+37
-63
lines changed

1 file changed

+37
-63
lines changed

doc/src/devdocs/offset-arrays.md

Lines changed: 37 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
# [Arrays with custom indices](@id man-custom-indices)
22

3-
Julia 0.5 adds experimental support for arrays with arbitrary indices. Conventionally, Julia's
3+
Conventionally, Julia's
44
arrays are indexed starting at 1, whereas some other languages start numbering at 0, and yet others
55
(e.g., Fortran) allow you to specify arbitrary starting indices. While there is much merit in
66
picking a standard (i.e., 1 for Julia), there are some algorithms which simplify considerably
7-
if you can index outside the range `1:size(A,d)` (and not just `0:size(A,d)-1`, either). Such
8-
array types are expected to be supplied through packages.
7+
if you can index outside the range `1:size(A,d)` (and not just `0:size(A,d)-1`, either).
8+
To facilitate such computations, Julia supports array with arbitrary indices.
99

1010
The purpose of this page is to address the question, "what do I have to do to support such arrays
1111
in my own code?" First, let's address the simplest case: if you know that your code will never
1212
need to handle arrays with unconventional indexing, hopefully the answer is "nothing." Old code,
1313
on conventional arrays, should function essentially without alteration as long as it was using
1414
the exported interfaces of Julia.
15+
If you find it more convenient to just force your users to supply traditional arrays where indexing starts at one, you can add
16+
17+
```julia
18+
@assert !Base.is_non1_indexed(arrays...)
19+
```
20+
21+
where `arrays...` is a list of the array objects that you wish to check for anything that
22+
violates 1-based indexing.
1523

1624
## Generalizing existing code
1725

1826
As an overview, the steps are:
1927

2028
* replace many uses of `size` with `axes`
2129
* replace `1:length(A)` with `eachindex(A)`, or in some cases `LinearIndices(A)`
22-
* replace `length(A)` with `length(LinearIndices(A))`
2330
* replace explicit allocations like `Array{Int}(size(B))` with `similar(Array{Int}, axes(B))`
2431

2532
These are described in more detail below.
2633

27-
### Background
34+
### Things to watch out for
2835

29-
Because unconventional indexing breaks deeply-held assumptions throughout the Julia ecosystem,
30-
early adopters running code that has not been updated are likely to experience errors. The most
31-
frustrating bugs would be incorrect results or segfaults (total crashes of Julia). For example,
36+
Because unconventional indexing breaks many people's assumptions that all arrays start indexing with 1, there is always the chance that using such arrays will trigger errors.
37+
The most
38+
frustrating bugs would be incorrect results or segfaults (total crashes of Julia).
39+
For example,
3240
consider the following function:
3341

3442
```julia
@@ -42,16 +50,10 @@ function mycopy!(dest::AbstractVector, src::AbstractVector)
4250
end
4351
```
4452

45-
This code implicitly assumes that vectors are indexed from 1. Previously that was a safe assumption,
46-
so this code was fine, but (depending on what types the user passes to this function) it may no
47-
longer be safe. If this code continued to work when passed a vector with non-1 indices, it would
48-
either produce an incorrect answer or it would segfault. (If you do get segfaults, to help locate
53+
This code implicitly assumes that vectors are indexed from 1; if `dest` starts at a different index than `src`, there is a chance that this code would trigger a segfault.
54+
(If you do get segfaults, to help locate
4955
the cause try running julia with the option `--check-bounds=yes`.)
5056

51-
To ensure that such errors are caught, in Julia 0.5 both `length` and `size`**should** throw an
52-
error when passed an array with non-1 indexing. This is designed to force users of such arrays
53-
to check the code, and inspect it for whether it needs to be generalized.
54-
5557
### Using `axes` for bounds checks and loop iteration
5658

5759
`axes(A)` (reminiscent of `size(A)`) returns a tuple of `AbstractUnitRange` objects, specifying
@@ -62,14 +64,7 @@ is `axes(A, d)`.
6264
Base implements a custom range type, `OneTo`, where `OneTo(n)` means the same thing as `1:n` but
6365
in a form that guarantees (via the type system) that the lower index is 1. For any new [`AbstractArray`](@ref)
6466
type, this is the default returned by `axes`, and it indicates that this array type uses "conventional"
65-
1-based indexing. Note that if you don't want to be bothered supporting arrays with non-1 indexing,
66-
you can add the following line:
67-
68-
```julia
69-
@assert all(x->isa(x, Base.OneTo), axes(A))
70-
```
71-
72-
at the top of any function.
67+
1-based indexing.
7368

7469
For bounds checking, note that there are dedicated functions `checkbounds` and `checkindex` which
7570
can sometimes simplify such tests.
@@ -115,40 +110,11 @@ then "wrap" it in a type that shifts the indices.)
115110
Note also that `similar(Array{Int}, (axes(A, 2),))` would allocate an `AbstractVector{Int}`
116111
(i.e., 1-dimensional array) that matches the indices of the columns of `A`.
117112

118-
### Deprecations
119-
120-
In generalizing Julia's code base, at least one deprecation was unavoidable: earlier versions
121-
of Julia defined `first(::Colon) = 1`, meaning that the first index along a dimension indexed
122-
by `:` is 1. This definition can no longer be justified, so it was deprecated. There is no provided
123-
replacement, because the proper replacement depends on what you are doing and might need to know
124-
more about the array. However, it appears that many uses of `first(::Colon)` are really about
125-
computing an index offset; when that is the case, a candidate replacement is:
126-
127-
```julia
128-
indexoffset(r::AbstractVector) = first(r) - 1
129-
indexoffset(::Colon) = 0
130-
```
131-
132-
In other words, while `first(:)` does not itself make sense, in general you can say that the offset
133-
associated with a colon-index is zero.
134-
135113
## Writing custom array types with non-1 indexing
136114

137115
Most of the methods you'll need to define are standard for any `AbstractArray` type, see [Abstract Arrays](@ref man-interface-array).
138116
This page focuses on the steps needed to define unconventional indexing.
139117

140-
### Do **not** implement `size` or `length`
141-
142-
Perhaps the majority of pre-existing code that uses `size` will not work properly for arrays with
143-
non-1 indices. For that reason, it is much better to avoid implementing these methods, and use
144-
the resulting `MethodError` to identify code that needs to be audited and perhaps generalized.
145-
146-
### Do **not** annotate bounds checks
147-
148-
Julia 0.5 includes `@boundscheck` to annotate code that can be removed for callers that exploit
149-
`@inbounds`. Initially, it seems far preferable to run with bounds checking always enabled (i.e.,
150-
omit the `@boundscheck` annotation so the check always runs).
151-
152118
### Custom `AbstractUnitRange` types
153119

154120
If you're writing a non-1 indexed array type, you will want to specialize `axes` so it returns
@@ -223,15 +189,23 @@ Base.reshape(A::AbstractArray, shape::Tuple{ZeroRange,Vararg{ZeroRange}}) = ...
223189

224190
and you can `reshape` an array so that the result has custom indices.
225191

226-
## Summary
192+
### For objects that mimic AbstractArray but are not subtypes
193+
194+
`is_non1_indexed` has a specialized implementation for `AbstractArrays`
195+
(just checking their `axes`) and defaults to `false` for anything else.
196+
If you have created some kind of object that mimics `AbstractArray` without being a subtype, and its
197+
axes start at something other than 1, then you should consider defining a method
198+
```julia
199+
Base.is_non1_indexed(obj::MyNon1IndexedArraylikeObject) = true
200+
```
201+
The purpose of this definition is to allow code that assumes 1-based indexing
202+
to detect a problem and throw a helpful error, rather than returning incorrect
203+
results or segfaulting julia.
204+
205+
### Catching errors
227206

228-
Writing code that doesn't make assumptions about indexing requires a few extra abstractions, but
229-
hopefully the necessary changes are relatively straightforward.
207+
If your new array type triggers errors in other code, one helpful debugging step can be to comment out `@boundscheck` in your `getindex` and `setindex!` implementation.
208+
This will ensure that every element access checks bounds. Or, restart julia with `--check-bounds=yes`.
230209

231-
As a reminder, this support is still experimental. While much of Julia's base code has been updated
232-
to support unconventional indexing, without a doubt there are many omissions that will be discovered
233-
only through usage. Moreover, at the time of this writing, most packages do not support unconventional
234-
indexing. As a consequence, early adopters should be prepared to identify and/or fix bugs. On
235-
the other hand, only through practical usage will it become clear whether this experimental feature
236-
should be retained in future versions of Julia; consequently, interested parties are encouraged
237-
to accept some ownership for putting it through its paces.
210+
In some cases it may also be helpful to temporarily disable `size` and `length` for your new array type,
211+
since code that makes incorrect assumptions frequently uses these functions.

0 commit comments

Comments
 (0)