First, let’s focus on the function countvecs
below.
julia> function countvecs(args::Union{AbstractVector{<: Number}, Number}...)
count(args) do arg
arg isa AbstractVector
end
end
countvecs (generic function with 1 method)
julia> code_llvm(countvecs, Tuple{UnitRange{Int}, Int, Int})
; @ REPL[18]:1 within `countvecs`
define i64 @julia_countvecs_892([2 x i64]* nocapture readonly %0, i64 signext %1, i64 signext %2) #0 {
top:
ret i64 1
}
julia> code_llvm(countvecs, Tuple{UnitRange{Int}, Vector{Int}, Int})
; @ REPL[18]:1 within `countvecs`
define i64 @julia_countvecs_894([2 x i64]* nocapture readonly %0, {}* %1, i64 signext %2) #0 {
top:
; @ REPL[18]:2 within `countvecs`
ret i64 2
}
julia> code_llvm(countvecs, Tuple{UnitRange{Int}, Vector{Int}, Vector{Int}})
; @ REPL[18]:1 within `countvecs`
define i64 @julia_countvecs_896([2 x i64]* nocapture readonly %0, {}* %1, {}* %2) #0 {
top:
; @ REPL[18]:2 within `countvecs`
ret i64 3
}
We can see that given arguments of concrete types, the function is constant. This gives is the dimensionality of the output array, allowing for complete type stability.
julia> function drop_singletons(
A::Array{T},
args...
) where {T}
N = countvecs(args...)
sz = NTuple{N,Int}(
[length(a) for a in args if !(a isa Number)]
)
reshape(A, sz)::Array{T,N}
end
drop_singletons (generic function with 1 methods)
julia> @code_warntype drop_singletons(zeros(2,1,3), 1:2, 5, 1:3)
MethodInstance for drop_singletons(::Array{Float64, 3}, ::UnitRange{Int64}, ::Int64, ::UnitRange{Int64})
from drop_singletons(A::Array{T}, args...) where T @ Main REPL[57]:1
Static Parameters
T = Float64
Arguments
#self#::Core.Const(drop_singletons)
A::Array{Float64, 3}
args::Tuple{UnitRange{Int64}, Int64, UnitRange{Int64}}
Locals
#31::var"#31#33"
#30::var"#30#32"
sz::Tuple{Int64, Int64}
N::Int64
Body::Matrix{Float64}
1 ─ (N = Core._apply_iterate(Base.iterate, Main.countvecs, args))
│ %2 = Core.apply_type(Main.NTuple, N::Core.Const(2), Main.Int)::Core.Const(Tuple{Int64, Int64})
│ (#30 = %new(Main.:(var"#30#32")))
│ %4 = #30::Core.Const(var"#30#32"())
│ (#31 = %new(Main.:(var"#31#33")))
│ %6 = #31::Core.Const(var"#31#33"())
│ %7 = Base.Filter(%6, args)::Base.Iterators.Filter{var"#31#33", Tuple{UnitRange{Int64}, Int64, UnitRange{Int64}}}
│ %8 = Base.Generator(%4, %7)::Base.Generator{Base.Iterators.Filter{var"#31#33", Tuple{UnitRange{Int64}, Int64, UnitRange{Int64}}}, var"#30#32"}
│ %9 = Base.collect(%8)::Vector{Int64}
│ (sz = (%2)(%9))
│ %11 = Main.reshape(A, sz)::Matrix{Float64}
│ %12 = Core.apply_type(Main.Array, $(Expr(:static_parameter, 1)), N::Core.Const(2))::Core.Const(Matrix{Float64})
│ %13 = Core.typeassert(%11, %12)::Matrix{Float64}
└── return %13
I do want to point out that this will not drop axes such as [1]
or 1:1
.