Custom arrays for dispatch

I have a situation where I am working with several qualitatively different types of arrays (arrays of phases, arrays of complex field amplitudes, arrays of intensities…). Although the data for each such array is the same–just an array of real or complex numbers–I would like to be able to dispatch based on the type of array, and I am wondering what is the slickest way to do so.

What I would like to have in an ideal world is a macro that generates a new subtype of AbstractArray which is identical to a regular array in every way but name, so that I can write methods specific to this type. E.g. @newArray("PhaseArray") or something like that. If something of this sort exists, please let me know. If not, then I am looking for the most efficient way to obtain something equivalent.

I’ll mention that I know two ways to obtain something of this sort, neither of which I find fully satisfactory:

  1. I could generate a type to represent the entries of each type of array (e.g. Phase, Amplitude, Intensity, etc.), and then make methods for AbstractArray{T} with T one of my custom types. This has two disadvantages: Firstly, I don’t want to require that my types be represented by a specific numeric type (e.g. Float64), so I’d have to make a different type for each numeric representation. Second, I suspect that generating a bunch of custom types for all elements of the array would entail a performance hit.
  2. I can follow the protocol for interfacing with Abstract Arrays outlined in the manual. This is in fact exactly what I want, but the reason I find it unsatisfactory is that it requires a lot of code to do something quite simple. Each of my custom types is basically just struct myArray{T,N} <: AbstractArray{T,N} data::AbstractArray{T,N} end, and all standard array methods just get applied to the data field. To write custom array methods for each such new type would be fairly tedious.

So what is the best way to accomplish this? Also, I am open to the possibility that this is simply bad code design, and I should do it a different way entirely–please let me know if you think this is so.

1 Like

Out of curiosity, can you just write a short wrapper type around the array, dispatch on that, and then unpack the array within the specialized functions?

1 Like

That works ok. It just makes mathematical expressions more cumbersome, as you need to write .data everywhere. I might end up doing this, though.

You could just unpack the data field. It doesn’t allocate if you unpack the field into another variable, so the following should be fine

struct Wrapper{T}
data::T
end

function foo(a::Wrapper)
  x = a.data
  # do stuff with x 
end
1 Like

Your types could be parametric, so you would have Phase{T} etc. and Array{Phase{ComplexF64}, N} etc.

I don’t think so. But you would have to implement the basic arithmetic and other relevant operations for it, so you are perhaps just moving the problem.

As for your point 2., I also wish this were easier.

Have you considered attaching units to your data? Look at Unitful.jl.

2 Likes

Implementing custom arrays isn’t as bad as one might think. You’ll probably be interested in

and

https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array

What I ended up doing was creating a collection of types with no fields under an abstract Domain type to indicate the sort qualities my arrays represented, and then making one parametric array type which took a Domain subtype as a parameter. Something like this:

abstract type Domain end
struct RealPhase <: Domain end
struct Amplitude <: Domain end
struct Intensity <: Domain end

struct Field{S<:Domain,T<:Number,N} <: AbstractArray{T,N}
    data::AbstractArray{T,N}
end
Field{S}(array::AbstractArray{T,N}) where {S<:Domain,T<:Number,N} = Field{S,T,N}(array)

# Minimal methods to make these types function as arrays.  
# All arithmetic works on a Field, though the output will be some sort of array, and not a Field. 
Base.size(f::Field) = size(f.data)
Base.getindex(f::Field, i::Int) = getindex(f.data,i)
Base.setindex!(f::Field, v, i::Int) = setindex!(f.data, v, i)
Base.IndexStyle(::Type{<:Field})= IndexLinear()

This seems to me the slickest way to achieve what I wanted, as I don’t need to define any arithmetic for Domain types, and I only need to define array methods once.