Hi,
Aqua.jl complains about Unbound type parameters detected in the following MWE:
julia> module mymod
function foo(x::AbstractArray{<:Union{T,Missing}}) where {T<:Real}
m = .!ismissing.(x) .&& .!isnan.(x)
return [ifelse.(m, x, T(0))]
end
function foo(x::AbstractArray{<:Missing})
m = .!ismissing.(x) .&& .!isnan.(x)
return [ifelse.(m, x, 0)]
end
end
Main.mymod
julia> Aqua.test_unbound_args(mymod)
Unbound type parameters detected:
[1] foo(x::AbstractArray{<:Union{Missing, T}}) where T<:Real @ Main.mymod REPL[32]:3
Test Failed at /Users/ferreol/.julia/packages/Aqua/EPo21/src/unbound_args.jl:37
Expression: isempty(unbounds)
Evaluated: isempty(Method[foo(x::AbstractArray{<:Union{Missing, T}}) where T<:Real @ Main.mymod REPL[32]:3])
ERROR: There was an error during testing
I don’t really understand why as I think I have covered all the cases:
Yep, besides just being piracy, I’ve always found dispatching on a X{<:Union{T, Missing}} where T quite fraught to use correctly… I’m not even certain how they are working with the rules of Julia’s type system to begin with!
Even more generally, simply dispatching on eltypes can be prone to surprises. It’s not hard to end up with an array whose elements are all some T but whose eltype is something wider.
I was a bit provocative as I didn’t find a way to properly exclude the function (that is a constructor of a struct in my package).
Actually, I don’t really need those function as I’ve just added it to make my package more composable in the case I’ll register it.
I never use missing because it usually ruins the performance:
julia> N = 1_000
1000
julia> g(x) = sum(x->ifelse(ismissing(x), 0 ,x),x,dims=2)
g (generic function with 1 method)
julia> h(x) = sum(x->ifelse(isnan(x), 0 ,x),x,dims=2)
h (generic function with 1 method)
julia> @btime g($([ifelse(rand().<0.4,missing,randn()) for _ in 1:N, y in 1:N ]));
4.201 ms (3 allocations: 9.08 KiB)
julia> @btime h($([ifelse(rand().<0.4,NaN,randn()) for _ in 1:N, y in 1:N ]))
159.169 μs (3 allocations: 8.08 KiB)
I don’t really understand what do you mean by piracy in this case?
It’s sure that if T is not concrete then dispatching on X{<:Union{T, Missing}} is a bit useless but not faulty. Out of curiosity, is there a prefered mechanism to concretize an array (let say an Vector{Number} where every elements are in fact Float64), or to dispatch on concrete vs non-concrete T