CustomUnitRanges
July 8, 2022 ยท View on GitHub
This Julia package supports the creation of array types with
"unconventional" indices, i.e., when the indices may not start at 1.
With this package, each custom array type can have a corresponding
axes range type, consequently providing a means for consistency
in allocation by similar.
See https://docs.julialang.org/en/v1/devdocs/offset-arrays/ for more information about defining and using array types with non-1 indices.
What's in this package
Currently this package defines two AbstractUnitRange types:
-
ZeroRange, whereZeroRange(n)is the equivalent of0:n-1, except that Julia's type system knows that the lower bound is 0. (This is analogous toBase'sOneTotype.) This is useful for defining arrays that are indexed starting with 0. -
URange, a parallel toUnitRange, for defining arbitrary range indices.
Usage
This package has a somewhat atypical usage: you should include files
from this repository at the source level. The reason is that this
package's range types should be private to the module that needs
them; consequently you don't want to define a module in the global
namespace.
Instead, suppose you're defining an array type that supports arbitrary indices. In broad terms, your module might look like this:
module MyArrayType
using CustomUnitRanges: filename_for_urange
include(filename_for_urange)
struct MyArray{T,N} <: AbstractArray{T,N}
...
end
Base.axes(A::MyArray) = Base.Slice.(map(URange, #=starting indices=#, #=ending indices=#))
...
end
Here,
using CustomUnitRanges: filename_for_urange
brings a non-exported string, filename_for_urange, into the scope of
MyArrayType. The key line is the include(filename_for_urange)
statement, which will load (at source-level) the code for the URange
type into your MyArrayType module. We chose "URange.jl" because
here we want arbitrary indices; had we wanted zero-based indices, we
would have chosen "ZeroRange.jl" instead. Second, note that the
output of axes is a Slice containing a URange type. More specifically, it's
creating a tuple of slices with MyArrayType.URange---there is no "global"
URange type, so the indices-tuple is therefore specific to this
package.
The important result is that two packages, defining MyArray and
OtherArray, can independently exploit URange. If MyArrayType
includes the specialization
function Base.similar(f::Union{Type,Function}, shape::Tuple{URange,Vararg{URange}}
MyArray(f(map(length, shape)), #=something for the offset=#)
end
and similarly for OtherArrayType. Then, if A is a MyArray and
B is an OtherArray,
similar(Array{Int}, axes(A))will create anotherMyArraysimilar(Array{Int}, axes(B))will create anotherOtherArray
despite the fact that they both use URange.