Success! Ignore the redundant pmap function - just a testcase. The point is that the hack results in the correct inferral. Seems like there should be a more built-in way to do this.
The built-in way to do it is: Core.Compiler.return_type(fn, Tuple{Any}) (and this is likely what gets called in the end) but I think eltype(map(fn, [])) is a nice way of writing it.
Since first is type-stable, wouldn’t it be simpler to use something like this?
typeof(fn(first(itr)))
Also, I’m not sure that the two-stage process is needed. For example:
function pmap3(fn, itr)
T = typeof(fn(first(itr)))
results = Vector{T}(undef, length(itr))
i = 1
for x in itr
@inbounds results[i] = fn(x)
i+=1
end
return results
end
yields
julia> using Test
# "simple" case of an Array
julia> @inferred pmap3(x->x/2, [1, 2])
2-element Array{Float64,1}:
0.5
1.0
# More complex case with a generator
julia> @inferred pmap3(x->x/2, (1//i for i in 1:2))
2-element Array{Rational{Int64},1}:
1//2
1//4
However, this does not seem to work when type instabilities are involved:
julia> @inferred pmap3(x->x%2==0 ? 0 : 0.1, [1, 2])
ERROR: return type Array{Float64,1} does not match inferred return type Array{_A,1} where _A
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] top-level scope at REPL[5]:1
You are correct. I noticed the same thing a little after the above posting and folded it in.
This is cute! I’ll go with this.
Actually, I think it still does (or is at least the same). The error you are seeing is the @inferred macro (properly) complaining about type stability. I get the same result with the longer map version.
Because of some quirky compiler trade-offs, pmap2(fn, itr) = pmap2(fn, itr, eltype(typeof(map(fn, [])))) will keep fn “generic”, and will not specialize on it. Use pmap2(fn::F, iter) where F = ... Function and DataTypes are the only objects treated this way.