Quite often I have parametric types where the field types depend in some simple but non-trivial way on the type parameters. Something like
struct Dada{N}
a::Array{Float64, N}
b::Array{Float64, N+1}
end
The above isn’t valid Julia, and I’ve understood that the recommended way to deal with such cases, assuming one wants to maintain type stability, is
struct DadaImp1{N, Ta, Tb}
a::Ta
b::Tb
function DadaImp1{N}(a::Array{Float64}, b::Array{Float64}) where {N}
Ta = Array{Float64, N}
Tb = Array{Float64, N+1}
a::Ta
b::Tb
return new{N, Ta, Tb}(a, b)
end
end
Now I’ve recently been using this pattern when building a library, which in turn uses types from another library that makes extensive use of this pattern, and I’m starting to end up with pretty ridiculous types such as
ModifiedBinaryLayer{TensorMap{ℤ₂Space,2,2,ℤ₂,TensorKit.SortedVectorDict{ℤ₂,Array{Complex{Float64},2}},FusionTree{ℤ₂,2,0,1,Nothing},FusionTree{ℤ₂,2,0,1,Nothing}},TensorMap{ℤ₂Space,2,1,ℤ₂,TensorKit.SortedVectorDict{ℤ₂,Array{Complex{Float64},2}},FusionTree{ℤ₂,2,0,1,Nothing},FusionTree{ℤ₂,1,0,0,Nothing}},TensorMap{ℤ₂Space,2,1,ℤ₂,TensorKit.SortedVectorDict{ℤ₂,Array{Complex{Float64},2}},FusionTree{ℤ₂,2,0,1,Nothing},FusionTree{ℤ₂,1,0,0,Nothing}}}
Most of that information is entirely redundant, the non-trivial part would simply be ModifiedBinaryLayer{ℤ₂Space, Array{Complex{Float64}}}
.
I don’t know if these ballooning type parametrizations affect inference (please enlighten me if you can), but they definitely affect my sanity when reading error messages, @code_warntype
, and various other things. Hence, I’ve been thinking of doing instead something like this:
struct DadaImp2{N}
a::Array{Float64}
b::Array{Float64}
function DadaImp2{N}(a::Array{Float64}, b::Array{Float64}) where {N}
Ta = Array{Float64, N}
Tb = Array{Float64, N+1}
a::Ta
b::Tb
return new{N}(a, b)
end
end
function Base.getproperty(d2::DadaImp2{N}, s::Symbol) where {N}
if s === :a
T = Array{Float64, N}
elseif s === :b
T = Array{Float64, N+1}
else
T = Any
end
return getfield(d2, s)::T
end
(For most purposes?) type stability is still maintained, since the compiler can figure out
what type d2.b
should be.
Assuming my fields aren’t bits types, but pointers, are there any downsides to this way of doing things?
PS. The optimal solution would be for this to get implemented: https://github.com/JuliaLang/julia/issues/18466 No idea if that’s in the cards though.