Get array type (e.g. Array, CuArray, etc.) from concrete array

This should be easy, but I haven’t found a way to do it.

I want a function that takes some concrete array type (<: AbstractArray), that might be backed by Array, or CuArray, or ROCArray or whatever, and returns the unparameterised constructor. e.g.

arraytype(::Array{T, N}) -> Array

or

arraytype(::CuArray{T, N}) -> CuArray

I want to do this in a way that doesn’t require knowing, ahead of time, the concrete array types.

Something like:

function arraytype(::A{T, N}) where {T <: Type, N <: Int, A <: AbstractArray}
    return A
end

(Which isn’t legal code: TypeError: in Type{...} expression, expected UnionAll, got a value of type TypeVar)

Is something like this possible?

julia> a
3-element Vector{Int64}:
 1
 2
 3

julia> typeof(a).name.wrapper
Array

Thanks, seems to work! I thought this kind of runtime reflection would introduce type instability but that doesn’t seem to be the case (?).

Note that unless you can find about datatype.name.wrapper in the Julia documentation, this is an implementation detail and can break with any Julia version (including a patch one). I am not sure there is a generic and safe way to do this.

Yeah right - good point. Unmarking it as a solution for now then.

Do you have a clear idea what you will use this for, and could this be an xy-problem?

Most cases of people asking for this are covered by similar.

Even though this function will not work either way, there are some extra problems with it.

Let’s take an example: Array{Float64, 2}, so that T is Float64, and N is 2. Note that

julia> Float64 <: Type
false

julia> 2 <: Int
ERROR: TypeError: in <:, expected Type, got a value of type Int64

The type parameters are not in that kind of subtype relationship. Instead they are in an isa relationship:

julia> Float64 isa Type
true

julia> 2 isa Int
true

This one, however, is correct:

julia> Array <: AbstractArray
true

So you are not using the type constraints correctly; the correct way is

arraytype(::A{T, N}) where {T <: Any, N, A <: AbstractArray}

The constraint on T must be in the type hierarchy of Float64 (and Type isn’t):

julia> supertypes(Float64)
(Float64, AbstractFloat, Real, Number, Any)

And 2 has no type hierarchy at all:

julia> supertypes(2)
ERROR: MethodError: no method matching supertypes(::Int64)

This is why you always see N unconstrained in functions that take arrays.

1 Like

@DNF There are times when you need to replace the underlying storage of an array based on some runtime configuration.

In my case, this comes down to the tricky business of writing code that is generic for CPU/CUDA/ROCM/(ONEAPI?). I am aiming to do this in a type stable way, and using the input types to signal which device should be doing the computing. This is usually straightforward, and I do use similar() a lot (as previous commenter mentioned).

However, oftentimes the array wrapper can make this fairly elegant, e.g. I might need to provision some data first on the host, then send to the device: data_device = wrapper(data) where wrapper = Array | CuAray | ROCArray. Or similarly, I use StructArrays which includes the method replace_storage(CuArray, mystructarray), and this also needs the array wrapper type.

One could pass the wrapper around as an argument to all functions, but this seems a little yucky.

Essentially, this is about managing runtime array storage in a way that propagates through my program in a type stable way. Definitely open to ideas about how other people have manged this.

Thanks for the explanation, but perhaps this would be clearer if you could show a usage example?