# How to convert element type of a container?

Suppose I have a container, `a`, could be a `Tuple`, a `Vector`, or a user-defined subtype of `AbstractArray`. Now I want to convert every element of `a` to another type, e.g., from `Int`s to `Float64`. For example, I write a rough function:

``````convert_eltype(T::Type, a) = map(x -> convert(T, x), a)
``````

This function works fine with `Tuple` and `Vector`, but not the last one.

``````julia> using StaticArrays

julia> struct A{T} <: FieldVector{2, T}
x::T
y::T
end

julia> convert_eltype(Float64, A(1, 2))
2-element SArray{Tuple{2},Float64,1,2}:
1.0
2.0
``````

This breaks the container type `A`:

``````julia> f(::A) = 1
f (generic function with 1 method)

julia> f(convert_eltype(Float64, A(1, 2)))
ERROR: MethodError: no method matching f(::SArray{Tuple{2},Float64,1,2})
Closest candidates are:
f(::A) at REPL[7]:1
Stacktrace:
[1] top-level scope at none:0
``````

I also tried other ways:

``````convert_eltype(T::Type, a) = typeof(a)(map(x -> convert(T, x), a))
``````

But it does not work. `typeof(a)` contains the `Int` information which makes `map(x -> convert(T, x), a)` work in vain.
I try to strip the container type but am told it seems not possible. I do not find `similar` useful because it just constructs a new uninitialized container, not converting one type to another.

Define a `StaticArrays.similar_type` method for your `FieldVector`. See Is there a way to seamlessly inherit vector arithmetic on new types?.

2 Likes

That’s a good way of doing this. I have read the doc of `similar_type`, and I found that this only works with concrete type `AA`:

``````julia> using StaticArrays

julia> import StaticArrays: similar_type

julia> export similar_type

julia> struct AA{T} <: FieldVector{2, T}
x::T
y::T
end

julia> convert_eltype(T::Type, a) = map(x -> convert(T, x), a)
convert_eltype (generic function with 1 method)

julia> similar_type(::Type{A}, ::Type{T}, size::Size) where {A <: AA, T} = AA{T}
similar_type (generic function with 18 methods)

julia> similar_type(AA, Float64, Size(2))
AA{Float64}

julia> convert_eltype(Float64, AA(1, 2))
2-element AA{Float64}:
1.0
2.0
``````

But if `AA` is an abstract type and it has several subtypes, this will break:

``````julia> abstract type M{N, T} <: FieldVector{N, T} end

julia> struct AA{T} <: M{2, T}
x::T
y::T
end

julia> convert_eltype(T::Type, a) = map(x -> convert(T, x), a)
convert_eltype (generic function with 1 method)

julia> similar_type(::Type{A}, ::Type{T}, size::Size) where {A <: M, T} = A{T}
similar_type (generic function with 18 methods)

julia> similar_type(AA, Float64, Size(2))
AA{Float64}

julia> convert_eltype(Float64, AA(1, 2))
ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{AA{Int64}}
Stacktrace:
[1] similar_type(::Type{AA{Int64}}, ::Type{Float64}, ::Size{(2,)}) at ./REPL[7]:1
[2] macro expansion at /Users/qz/.julia/packages/StaticArrays/mcf7t/src/mapreduce.jl:30 [inlined]
[3] _map(::getfield(Main, Symbol("##3#4")){DataType}, ::Size{(2,)}, ::AA{Int64}) at /Users/qz/.julia/packages/StaticArrays/mcf7t/src/mapreduce.jl:21
[4] map at /Users/qz/.julia/packages/StaticArrays/mcf7t/src/mapreduce.jl:11 [inlined]
[5] convert_eltype(::Type, ::AA{Int64}) at ./REPL[6]:1
[6] top-level scope at none:0
``````

It does not seem to be my problem: The error happens in StaticArrays.jl. So the only way is that I add a method for each subtype of `M`?

This is basically the same question you asked in How to get the container type of a container?. You’ll have to define a `similar_type` overload for each concrete `FieldVector` subtype you define. If that’s too much of a hassle, you could write a macro to generate the `similar_type` method along with the `FieldVector` subtype definition.

Note by the way that you’ll want to be more specific with the `Size` argument:

``````StaticArrays.similar_type(::Type{<:AA}, ::Type{T}, ::Size{(2,)}) where {T} = AA{T}
``````

otherwise,

``````julia> aa = AA(1, 2)
2-element AA{Int64}:
1
2

julia> [aa; aa]
ERROR: DimensionMismatch("No precise constructor for AA{Int64} found. Length of input was 4.")
Stacktrace:
[1] AA{Int64}(::Tuple{Tuple{Tuple{NTuple{4,Int64}}}}) at /Users/tkoolen/.julia/packages/StaticArrays/mcf7t/src/convert.jl:1
[2] Type at /Users/tkoolen/.julia/packages/StaticArrays/mcf7t/src/convert.jl:4 [inlined] (repeats 3 times)
[3] macro expansion at /Users/tkoolen/.julia/packages/StaticArrays/mcf7t/src/linalg.jl:114 [inlined]
[4] _vcat at /Users/tkoolen/.julia/packages/StaticArrays/mcf7t/src/linalg.jl:96 [inlined]
[5] vcat(::AA{Int64}, ::AA{Int64}) at /Users/tkoolen/.julia/packages/StaticArrays/mcf7t/src/linalg.jl:92
[6] top-level scope at none:0
``````
1 Like

That’s indeed why I asked this question. I am surprised that Julia does not provide a simple way of doing this.

The key thing is that Julia doesn’t enforce that the first type parameter of `A` here is the same type parameter that appears in `M{_, T}`. In fact, another person could come along and define a

``````      struct BB{T, S, R} <: M{2, R}
x::R
y::R
bonus::T
more::S
end
``````

Now your generic `similar_type` method is no longer correct.

4 Likes