I’ve reduced my problem to a minimal working example:
using LinearAlgebra
function alt(x::Vector{Symmetric{T}}) where {T <: Real}
return T
end
alt(Vector{Symmetric{Float64}}(undef, 1))
When I try to run this function, it errors out saying that T is undefined!
However, any of these other alternatives work:
function alt(x::Symmetric{T}) where {T <: Real}
return T
end
function alt(x::Union{Symmetric{T}, Nothing}) where {T <: Real}
return T
end
And the funny thing is that the parametric type definition works, because the method is correctly dispatched; I can even Juno.@enter into it and run the function up to the line that tries to access the parametric type.
What am I missing here?
EDIT: Just adding here that this was tested with Julia 1.2.0 and Julia 1.1.1.
julia> using LinearAlgebra
julia> function alt(x::Vector{<: Symmetric{T}}) where {T <: Real}
return T
end
alt (generic function with 1 method)
julia> alt(Vector{Symmetric{Float64}}(undef, 1))
ERROR: UndefVarError: T not defined
Stacktrace:
[1] alt(::Array{Symmetric{Float64,S} where S<:(AbstractArray{#s617,2} where #s617<:Float64),1}) at ./REPL[2]:2
[2] top-level scope at REPL[3]:1
I would like to note that the function I wrote works and is correctly dispatched: the only problem occurs upon using the parametric type T within the body.
It actually works, but that’s not so good as it only means I don’t have a minimal working example
I was making my function types parametric, and had this problem with the following function:
My actual function is this
function smoother(F::Matrix{RT},
G::Matrix{RT},
a::Vector{Vector{RT}},
R::Vector{Symmetric{RT}},
m::Vector{Vector{RT}},
C::Vector{Symmetric{RT}}) where {RT <: Real}
n, p = dlm_dimension(F, G)
T = size(R, 1)
s = Vector{Vector{RT}}(undef, T)
S = Vector{Symmetric{RT}}(undef, T)
s[T] = m[T]
S[T] = C[T]
for t = T-1:-1:1
B = C[t] * G' * inv(R[t+1])
s[t] = m[t] + B * (s[t+1] - a[t+1])
S[t] = C[t] - Symmetric(B * (R[t+1] - S[t+1]) * B')
end
return s, S
end
As it is, it doesn’t work because it complains RT is undefined.
If I change the Vector{Symmetric{RT}} into Vector{Symmetric{Float64}} the problem disappears. I am in fact calling it with filled up parameters (not undef as the previous mwe).
I think the fact that it’s possible for T to be undefined even though dispatch selects the method stems from https://github.com/JuliaLang/julia/pull/23117, but I don’t quite understand why T should be undefined in this case.
But what’s the reason that it shouldn’t work for Vector{Symmetric{Float64}}(undef, 1), or e.g. Symmetric{Float64}[Symmetric(rand(3, 3))], a perfectly valid use case?
Hey @tkoolen, an interesting thing for me is that in my actual example (see post above yours), even though RT is undefined at runtime, I cannot 'compile a function that has the line
RT = typeof(F[1])
because it complains of conflict with the type parameter
Although this appears to be an interesting corner case (at least to me), in your production code you should probably stop doing stuff like
S = Vector{Symmetric{RT}}(undef, T)
for performance reasons. Note that something like Vector{Symmetric{Float64}} is not a concrete type (it’s a UnionAll); Symmetric has a second type parameter. As a result, you’re incurring a performance cost due to dynamic dispatch any time you touch S. Additionally, your function only accepts stuff like Vector{Symmetric{Float64}}(undef, 1) (a Vector with non-concrete element type), not e.g. [Symmetric(rand(3, 3))] (which does have concrete element type). So you’re inconveniencing callers of the function by forcing them to use a suboptimal storage type, which can be fixed by doing what @Raf proposed in Cannot use parametric type within function body - #2 by Raf. Once that’s done, you could just use S = similar(C, T) to simultaneously clean up the code and fix the non-concrete element type issue for S.
I’m no type-system expert, but to me it still kind of seems like a bug, e.g. in OPs first example, how can T simultaneously be undefined but also have been verified to be T<:Real ?
I agree, or at least I don’t understand why this would have to be the case. Not sure if your earlier unwrap_unionall post is exactly related though. Another related paradoxical corner case is type parameter `UndefVarError` when dispatch matches `UnionAll` · Issue #29788 · JuliaLang/julia · GitHub, but it doesn’t seem the case in the OP is an exact duplicate of that either. Specifically, in this case there does appear to be a unique correct value for T.
I’m not sure why they don’t both work, I agree it seems like a bug. There is a correct, know value for T here and S being parametric shouldn’t affect that.
Edit: the original example doesn’t work because you need the <: subtype operator when the contained type is parametric, as you are not matching any specific types, but some Symmetric{T,S} from all the possible Symmetric{T,S}. Or something like that.
After some private help from @tkoolen and as pointed out by @Raf in the post above, the problem lied with the fact that Symmetric has two parameters.
Upon using Symmetric{RT, Matrix{RT}}everywhere I was previously using just the Symmetric{RT}, not only did the type instabilities (shown by @code_warntype in my previous function) go away but this change allowed me to access the value of RT within the function body.
This means that the instability on the second type from Symmetric was not allowing the access on the parametric type RT.
This was quite a ride and I want to thank everyone for being so helpful!