function my_mean(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
x1 = first(A) / n
_prom(x::T, y::S) where {T,S} = begin
R = promote_type(T, S)
return convert(R, x)
end
return sum(x->_prom(x,x1), A) / n
end
function my_mean_bad(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
x1 = first(A) / n
let T = typeof(first(A)/n)
return sum(x -> convert(promote_type(T, typeof(x)), x), A) / n
end
end
julia> @inferred(my_mean(Float32[]))
NaN32
julia> @inferred(my_mean_bad(Float32[]))
ERROR: return type Float32 does not match inferred return type Any
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope at REPL[5]:1
The problem is not with the closure, but in captured variable T
. If you replace T
with typeof(first(A)/n)
then all be ok. I’m not a big specialist in Julia compiler, but I think that if T
is a dynamic DataType
then the compiler can’t be sure that it has a constant value in all calls of promote_type, so at compilation time the result type of promote_type(T,…) is Any
and for convert
its Any
too
Probably more accurately it sounds like this: at compilation time of the closure we only know the type of T
, that is DataType
but not its value. So we cannot recognize the result of promote_type
and the result type of convert
at compilation time
This is missing a terminating end
. I think that you can just rewrite this as
function my_mean_bad(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
x1 = first(A) / n
sum(x -> first(promote(x, x1)), A) / n
end
which infers fine.
That version does not work if an array has “missing” values.
Your original question does not mention missing
at all though.
The other function does not infer either for missing
, eg
julia> @inferred(my_mean([missing 1f0]))
ERROR: return type Missing does not match inferred return type Any
What would you want to do for missing
values, ignore them (see skipmissing
), or return missing
as the mean?
Yes, the my_mean() version does not infer with the missing values as well, but it executes without an error, unlike the one you propose.
Edit: my_mean() is similar to Base.sum() on “missing”
julia> @inferred(Base.sum([1,missing]))
ERROR: return type Missing does not match inferred return type Union{Missing, Int64}
function my_mean_bad(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
return sum(x -> convert(promote_type(typeof(first(A)/n), typeof(x)), x), A) / n
end
infers fine and executes without an error with the missing values
The question isn’t how to fix the function, but why the type inference fails on the seemingly equivalent program.
Fixed, thanks
This version also breaks the inference
function my_mean_bad(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
x1 = first(A) / n
T = typeof(first(A)/n)
return sum(x -> convert(promote_type(T, typeof(x)), x), A) / n
end
I think you need to specialize on the type. This dummy function barrier works:
function my_mean_bad(A::AbstractArray)
isempty(A) && return sum(A)/0
n = length(A)
x1 = first(A) / n
_core(::Type{T}) where {T} = sum(x -> convert(promote_type(T, typeof(x)), x), A) / n
_core(typeof(x1))
end
Similarly _core(x1::T) where T
etc.
Yes, that’s another version that works. Probably, a manifestation of this issue