Right now I try to understand how specialization and parametric types interact. The following all came up when trying to design a sliding window iterator (iterator that shall return a slided view of an array in each iteration step)
Code so far, not yet working as we are missing an iterate function etc:
using IdentityRanges
struct SlidingWindowIterator #1
array::AbstractArray #2
range::CartesianIndices #3
validindices::CartesianIndices #3
end
#3 is the set of indices of array that have all needed indices forrange as valid indices around them.
Objectives: Make it specialize for the array type and the dimension (Are there other attributes that should be parametrized for performance reasons?). Also enforce all 3 arguments to be of the same dimension. And have a constructor that fills in all parameter types automatically. How to do that?
struct SlidingWindowIterator{T<:AbstractArray} #1
array::T #2
range::CartesianIndices #3
validindices::CartesianIndices #3
function SlidingWindowIterator(array::T, range, validindices) where T <: AbstractArray
if false # do your validation here
error("invalid indices")
end
return new{T}(array, range, validindices)
end
end
By parameterizing the type on the specific type of AbstractArray that array is, you define a family of types, each with a storage layout optimized for the specific array type that gets passed in.
The function inside the struct definition is an inner constructor, which becomes the only way to construct the type. You can perform any needed validation there.
Actually, you should also specialize the types of the indices, since CartesianIndices is also an abstract type. Here I gave both index ranges the same type, since they’re both supposed to be indices for the same array:
struct SlidingWindowIteratorTwo{T<:AbstractArray, I<:CartesianIndices} #1
array::T #2
range::I #3
validindices::I #3
function SlidingWindowIteratorTwo(array::T, range::I, validindices::I) where {T <: AbstractArray, I <: CartesianIndices}
if false # do your validation here
error("invalid indices")
end
return new{T, I}(array, range, validindices)
end
end
Assuming that I can collect the iteration values after collapsing each window into a single value, the validindices field will become the indices of the newly collected array. Thus they probably need to support non-1-based indexing. Likewise for range. Thus I will probably need different types for them.
Now, I need access to the dimensions of the array in order to implement the
I need the AT parameter because I need the N parameter for the dimension. Is there a better solution that doesn’t force me to have 5 type parameters (some of them nested) to use a single type parameter? Maybe even preventing to specialize on unneeded type params.
Base.IteratorSize(::Type{<:SlidingWindowIterator{AT}}) where AT = Base.HasShape{ndims(AT)}()
or that
Base.IteratorSize(::Type{<:SlidingWindowIterator{AT}}) where {AT<:AbstractArray} = Base.HasShape{ndims(AT)}()
It feels quite weird to use functions in the type parameters. Can this be efficiently compiled? Because the dimension is available at compiletime due to the type parameter but some functions are first called at runtime so this might cannot be optimized to the end. Is that correct?
Also, what to do if AT is not the first type parameter of SlidingWindowIterator?