Most compact way to create an array of a known container type with a different eltype?

For example, suppose that we have an array type such as Atype = Array{ComplexF64,3} and would like to create an array of the same container type (in this case Array) but with a different element type, say Int (so the target array type is Array{Int,K} for some dimension K). The size of the array to create is known. What is the most compact (and least allocating) way to achieve this?

If an instance of Atype already exists, we can use similar:

Atype = Array{ComplexF64,3}
a = Atype(undef, (10,10,10))  # size of existing array is (10,10,10)

sz = (3,4)  # size of target array is (3,4)
b = similar(a, Int, sz)

However, I don’t have an existing a::Atype and do not want to create such a. The best I have so far is something like

Atype = Array{ComplexF64,3}
sz = (3,4)
b = similar(Atype(undef, (0,0,0)), Int, sz)

but Atype(undef, (0,0,0)) is still allocating (though very small), and when the dimension of the array is parametrized, the method becomes a bit convoluted:

K = 3  # parametrized dimension
Atype = Array{ComplexF64,K}
sz = (3,4)
b = similar(Atype(undef, ntuple(k->0,Val(K))), Int, sz)

I wished I had something like

K = 3
Atype = Array{ComplexF64,K}
sz = (3,4)
b = similar(Atype, Int, sz)

but this does not work.

Is there a better way than shown above?

I’m sure it’s implied somewhere but why can’t you just do Array{Int}(undef, sz)? The only thing preserved from Atype is Array, so you can just use the constructor directly with the given inputs Int and sz.

1 Like

If you know the container, you can curry the parameters in later.

julia> const container = Array{<: Any,3}
Array{<:Any, 3}

julia> container{ComplexF64}
Array{ComplexF64, 3}

julia> container{Int}
Array{Int64, 3}

I suppose what you really want is as follows.

julia> function replace_eltype(
           A::Type{ <: AbstractArray{T,N}},
       ) where {T, N}
           Base.typename(A).wrapper{E, N}
replace_eltype (generic function with 1 method)

julia> Atype = Array{ComplexF64,3}
Array{ComplexF64, 3}

julia> replace_eltype(Atype, Int)
Array{Int64, 3}

The issue with replace_eltype or anything generically manipulating type parameters (with what I’m pretty sure are internals, not API) is that there’s no guaranteed link between parameter order and meaning within a parametric abstract type. For example, this doesn’t go as planned:

julia> struct DumbArray{Size,T,N} <: AbstractArray{T,N} end

julia> replace_eltype(DumbArray{9, Int, 3}, Bool)
DumbArray{Bool, 3}

For a real-world example, StaticArrays.jl. Functions that manipulate the eltype must dispatch on a parametric composite type to preserve it and specifically handle its parameters e.g. replace_eltype(::Type{DumbArray{Size,T,N}}, E::Type) where {Size,T,N} = DumbArray{Size,E,N}. Earlier I figured that if there was only 1 parametric composite type in mind, might as well cut to chase for Array instantiation; it’s not even feasible to involve replace_eltype because all the type parameters were actually replaced.

The iterated union fixing some parameters for a parametric composite type is solid though. Worth mentioning that const isn’t necessary for it to work, it’s just separately a good idea for global variables.

1 Like

I’ve wanted this in the past and I thought there was an issue about it, but I can’t find it now. The solution I imagine is that there would be a similar method taking a type instead of an instance (and the method taking an instance could call the one defined on a type, just like we already do e.g. for eltype).

Similar issue: fmap(float, xs::MyArray{Int64}) :: MyArray{Float64}

This may be somewhat off-topic: I often wish that also other array-related functions accepted a container type, specifically zeros and ones. Currently there are also falses and trues for BitArray and spzeros for SparseArray. If you create your own container type, you have to define yet other functions. This gives a whole zoo. As in the OP, it would be convenient if one could simply hook into zeros and define

zeros(MyArray, Int, 2, 3)