Package for arbitrary `getindex` function?

Does anyone know if we have an array package that lets you specify an arbitrary getindex function? Implementing it is pretty trivial; aside from tests, this is a complete package:

module ClosureArrays

export ClosureArray

struct ClosureArray{T,N,F,P,Axs} <: AbstractArray{T,N}
    getindex::F
    parent::P
    axes::Axs
end
ClosureArray{T}(getindex::F, parent, axes) where {T,F} =
    ClosureArray{T, length(axes), F, typeof(parent), typeof(axes)}(getindex, parent, axes)
ClosureArray(getindex::F, parent, axes) where F =
    ClosureArray{Base._return_type(getindex, Tuple{typeof(parent), Vararg{Int, length(axes)}})}(getindex, parent, axes)

Base.@propagate_inbounds Base.getindex(A::ClosureArray{T,N}, i::Vararg{Int,N}) where {T,N} =
    convert(T, A.getindex(A.parent, i...))::T
Base.size(A::ClosureArray) = map(length, A.axes)
Base.axes(A::ClosureArray) = A.axes

end

and here would be a demo of an array that reverses the dimensions (mimicking PermutedDimsArray):

julia> P = rand(3,5,4);

julia> A = ClosureArray(P, reverse(axes(P))) do AA, i...
           AA[reverse(i)...]
       end;

julia> A[3,2,1] == P[1,2,3]
true

and another that lazily sums along dimension 2:

julia> A = ClosureArray(P, (axes(P, 1), axes(P, 3))) do AA, i1, i2
           sum(view(AA, i1, :, i2))
       end;

julia> size(A)
(3, 4)

julia> A[1, 3] == sum(P[1, :, 3])
true

If we don’t have this already, I can add ClosureArrays to JuliaArrays. (Or should it be called GetindexArrays?)

6 Likes

I don’t know a package that implements this. I am curious, why do you explicitly store parent? What I expected was:

struct GetindexArray{...} <: ...
    f::F
    axes::Axes
end
Base.getindex(A::GetindexArray, I...) = A.f(I...)

A = GetindexArray((axes(P, 1), axes(P, 3))) do i1, i2
           sum(view(P, i1, :, i2))
       end;

I think I also have seen that variant in a package, but I don’t remember where.

1 Like

What does P reference inside the do block?

It is a capture P = rand(3,5,4).

If you do that at the command line, it’s a non-const global variable capture. I thought it would be nice to support efficient processing no matter where the parent comes from.

3 Likes

Efficiency is indeed nice and that’s a pragmatic solution. Thanks!

1 Like