How to properly unwrap nested structs to access internals?

Say I have the following architecture:


struct AArray <: AbstractArray
    arr
end

struct BArray <: AbstractArray
    data::AbstractArray
end

struct CArray <: AbstractArray
    cool_mapping::Dict
end

Where I can have AArray created the following ways:

AArray(BArray(CArray))
AArray(CArray)

I want to do two things:

  1. I want to ensure that AArray contains CArray either directly in arr or wrapped inside of a BArray, how can I do this?

  2. How can I access cool_mapping given an AArray regardless of whether it’s wrapped inside of BArray?

In reality, I’m trying to wrap an AxisArray containing potentially a ReshapedArray around a custom Array type or the custom Array type directly.

  1. Parametric types:
struct AArray{W<:Union{BArray,CArray}} <: AbstractArray
    arr::W
end
  1. Multiple dispatch:
coolmap(a::AArray{CArray}) = a.arr.cool_mapping
coolmap(a::AArray{BArray}) = a.arr.data.cool_mapping
1 Like

Another option for 2 is recursion which may be favourable if your real application has alot of combinations:

coolmap(a::AArray) = coolmap(a.arr)
coolmap(b::BArray) = coolmap(b.data)
coolmap(c::CArray) = c.cool_mapping
1 Like

Is there a performance penalty for doing the Union like this? I know that there is if I define the following

struct AArray <: AbstractArray
    arr::Union{BArray, CArray}
end

it’s significantly slower, but maybe your solution is less ambiguous to the compiler?

I made a typo in my original post. Type BArray is a general type that may or may not have a type CArray inside of it so coolmap should only be defined for BArrays containing CArrays, which is different than your answer I believe.

struct TheStruct{T<:SomeAbstractType}
    x::T
end

is very different in performance from

struct TheStruct
    x::SomeAbstractType
end

And that is the whole point with the parametric types.

In the latter case, the type TheStruct provides basically no information on what the inner layout of an object is, because one can subtype SomeAbstractType with arbitrarily complex type. So, the compiler can only produce slow generic code with unboxing etc.
In the former case, any concrete object cannot be of type TheStruct, it will be of type TheStruct{SomeType}. If SomeType is a concrete type, then the compiler has information of the data layout from the type of object alone and can produce fast machine code.

Moreover, all types in your OP must be parametric for performance:

struct CArray{T<:Dict} <: AbstractArray
    cool_mapping::T
end

struct BArray{T<:AbstractArray} <: AbstractArray
    data::T
end

const AllowableATypes = Union{CArray, BArray{<:CArray}}

struct AArray{T<:AllowableATypes} <: AbstractArray
    arr::T
end

Note that you can’t have BArray{CArray} in AllowableATypes, because that would disallow BArray{CArray{SomeConcreteDictType}} (cf. Array{Int} and Array{Integer}). You need a BArray parameterized by a type that is a subtype of CArray, that’s why a longer signature is required.

3 Likes