Vcat gives Any

v = [[1, 2, 3], [4, 5]]

f(v) = vcat(v...)

julia> f(v)
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> @code_warntype f(v);
MethodInstance for f(::Vector{Vector{Int64}})
  from f(v) @ Main REPL[87]:1
Arguments
  #self#::Core.Const(f)
  v::Vector{Vector{Int64}}
Body::Union{Vector{Any}, Vector{Int64}}
1 ─ %1 = Core._apply_iterate(Base.iterate, Main.vcat, v)::Union{Vector{Any}, Vector{Int64}}
└──      return %1

dunno why vcat() produces a Union{Vector{Any}, Vector{Int64}} rather than just a Vector{Int64}? The Vector{Any} would generate warntypes in subsequent calls.

Can it be avoided? Thanks

I think it’s just the splatting:

julia> f(v) = reduce(vcat, v)
f (generic function with 1 method)

julia> @code_warntype f(v)
MethodInstance for f(::Vector{Vector{Int64}})
  from f(v) @ Main REPL[13]:1
Arguments
  #self#::Core.Const(f)
  v::Vector{Vector{Int64}}
Body::Vector{Int64}
1 ─ %1 = Main.reduce(Main.vcat, v)::Vector{Int64}
└──      return %1
4 Likes

Thanks! But why splatting in particular vcat(v...) causes the problem? Other use cases (i.e. not using vcat) of splatting seem to work fine???

It has been said that splatting can be very taxing on the compiler, so maybe there are some heuristics to limit inference in this case to ensure compile times remain reasonable.

Here, splatting is faster! (julia 1.9.2)

julia> v=[[1,2,3],[4,5]]
2-element Vector{Vector{Int64}}:
 [1, 2, 3]
 [4, 5]

julia> @btime vcat($v...)
  63.342 ns (1 allocation: 96 bytes)
5-element Vector{Int64}:
 1
 2
 3
 4
 5

julia> @btime reduce(vcat,$v)
  72.224 ns (2 allocations: 176 bytes)
5-element Vector{Int64}:
 1
 2
 3
 4
 5

Yes it may be faster by itself. But the Any produced would cause warntypes in subsequent calls.

hm? In this case the type inference should be straightforward (by a human)…… maybe it should be considered a “bug”?

I think the Vector{Any} comes from the fact that v may be empty. Perhaps the handling of the empty case may be improved.

2 Likes

Note that the somewhat hacky way of defining the function f doesn’t lead to the inference issue:

julia> f(v) = vcat(v[1], @view(v[2:end])...)
f (generic function with 1 method)

julia> Test.@inferred f([[1, 2, 3], [4, 5]])
5-element Vector{Int64}:
 1
 2
 3
 4
 5

This ensures that there is at least one element to vcat, which rules out Vector{Any}.

2 Likes