How is the output type determined in map and friends?

Just curious, how is the container type determined for map?

It seem pretty magical to me:

julia> @code_warntype map(x -> x/2, rand(Int, 5))
1995 1 ─ %1 = %new(Base.Generator{Array{Int64,1},getfield(Main, Symbol("##3#4"))}, getfield(Main, Symbol("##3#4"))(), A)::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##3#4"))}                    │╻╷ Type
     │   %2 = invoke Base._collect(_3::Array{Int64,1}, %1::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##3#4"))}, $(QuoteNode(Base.EltypeUnknown()))::Base.EltypeUnknown, $(QuoteNode(Base.HasShape{1}()))::Base.HasShape{1})::Array{Float64,1}
     └──      return %2 

I guess it boils down to this: how is the output type of functions determined before they are evaluated in regular Julia? I understand that this is definitely something that the compiler should be able to figure out, but how would I be able to write a function that uses the compiler’s smarts to pre-allocate an output array?

It isn’t. The inference type is used for the empty case.

Don’t. Not for any non-empty case or at least not without making sure your code runs just fine without it.

1 Like

Good advice to keep it simple, but it looks like the inference type is used by map. I’m just curious how it works.

julia> @code_warntype map(x -> x/2, rand(Int, 0))
1995 1 ─ %1 = %new(Base.Generator{Array{Int64,1},getfield(Main, Symbol("##5#6"))}, getfield(Main, Symbol("##5#6"))(), A)::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##5#6"))}                    │╻╷ Type
     │   %2 = invoke Base._collect(_3::Array{Int64,1}, %1::Base.Generator{Array{Int64,1},getfield(Main, Symbol("##5#6"))}, $(QuoteNode(Base.EltypeUnknown()))::Base.EltypeUnknown, $(QuoteNode(Base.HasShape{1}()))::Base.HasShape{1})::Array{Float64,1}
     └──      return %2   

oh nevermind, I guess the code_warntype basically says how it’s done.

Yes, but

BTW, with union splitting, I wonder if this is still needed. map & friends could just return Any[] for empty collections.