Description
It seems that conversion to the abstract type AbstractArray{T,N}
is not implemented consistently in the array types of LinearAlgebra. Some types (Hermitian, Symmetric and Diagonal) explicitly support it, but others (Transpose, Adjoint and the triangular types) do so only implicitly. This leads to different behaviour, especially when using immutable arrays.
Perhaps this conversion is not widely used, but it is frequently useful at least to me :-) Writing convert(AbstractArray{T,N}, myarray)
is a convenient way to generically update the element type of a matrix, while retaining its structure. It can also be used merely to ensure that an array has a certain element type T
, and that is how it is used for example in the constructor of Diagonal{T}
and in many other places in LinearAlgebra.
Base defines the conversion convert(AbstractArray{T,N}, myarray)
and subsequently calls the abstract constructor AbstractArray{T,N}(myarray)
. The fallback of this constructor does:
copyto!(similar(myarray, T), myarray)
.
This fallback will always return a mutable array, even when myarray
was immutable. This makes sense for a fallback, otherwise the data of myarray
could not be copied back into the new array. But it means the result may not be optimal.
An example of this fallback at work is the following:
julia> using StaticArrays
julia> convert(AbstractVector{Int}, SVector(1,2))
2-element SArray{Tuple{2},Int64,1,2} with indices SOneTo(2):
1
2
julia> convert(AbstractVector{Float64}, SVector(1,2))
2-element MArray{Tuple{2},Float64,1,2} with indices SOneTo(2):
1.0
2.0
Conversion to AbstractVector{Int}
is fine, but changing the element type from Int
to Float64
suddenly makes the result a mutable array (MArray
rather than SArray
), which may be less efficient later on. This can of course be fixed in StaticArrays and it soon will be (see JuliaArrays/StaticArrays.jl#747).
However, in StaticArrays one can not fix the linear algebra types in stdlib. Fixing the example above, i.e., returning immutable SVector
and SArray
types upon conversion, one gets the following inconsistent behaviour when using Symmetric
or UpperTriangular
:
julia> s = Symmetric(SA[1 2; 2 3])
2×2 Symmetric{Int64,SArray{Tuple{2,2},Int64,2,4}}:
1 2
2 3
julia> convert(AbstractMatrix{Float64}, s)
2×2 Symmetric{Float64,SArray{Tuple{2,2},Float64,2,4}}:
1.0 2.0
2.0 3.0
julia> tri = UpperTriangular(SA[1 2; 0 3])
2×2 UpperTriangular{Int64,SArray{Tuple{2,2},Int64,2,4}}:
1 2
⋅ 3
julia> convert(AbstractMatrix{Float64}, tri)
2×2 UpperTriangular{Float64,MArray{Tuple{2,2},Float64,2,4}}:
1.0 2.0
⋅ 3.0
The point is that the symmetric type retains the immutable SArray
after conversion, but the triangular type now carries a mutable MArray
. The reason is that Symmetric
implements the AbstractMatrix{T}(A::Symmetric)
constructor and recursively converts its data, but UpperTriangular
doesn't. The result is correct, but in this case arguably not optimal.
A simpler example strictly within Base is:
julia> convert(AbstractVector{Float64}, 1:3)
3-element Array{Float64,1}:
1.0
2.0
3.0
This is the same fallback at work. One could argue that in some cases a better answer would be 1.0:1.0:3.0
(which is immutable).
These issues are easily fixed and I'd be happy to do so. In fact I already tested what would happen, see JuliaArrays/StaticArrays.jl#747 (comment). However, that depends on whether the "fix" is actually desirable.
Is convert(AbstractVector{Float64}, myvector)
a good way to generically change an element type?
Do developers currently assume that the output after this conversion is mutable (I found exactly one example of that assumption in SparseArrays here)?
Could this affect the promotion of arrays somewhere?
There have been several issues about the construction of array, but I found none specifically on the conversion to an abstract type.