How to make type-stable: passing an element of a vector of functions

I have an array of functions, and I need to iterate it and pass each element of the array to another function. How can I make it type-stable?

This code illustrates my problem:

function f(g::Function, x::Int64)
     return g(x)
end

g1(x) = x^2
g2(x) = (1 - x)^2
garr = [g1, g2]

function test()
    for k = 1 : 2
           f(garr[k], 20)
    end
end
julia> @code_warntype test()
Variables
  #self#::Core.Const(test)
  @_2::Union{Nothing, Tuple{Int64, Int64}}
  k::Int64

Body::Nothing
1 ─ %1  = (1:2)::Core.Const(1:2)
β”‚         (@_2 = Base.iterate(%1))
β”‚   %3  = (@_2::Core.Const((1, 1)) === nothing)::Core.Const(false)
β”‚   %4  = Base.not_int(%3)::Core.Const(true)
└──       goto #4 if not %4
2 β”„ %6  = @_2::Tuple{Int64, Int64}::Tuple{Int64, Int64}
β”‚         (k = Core.getfield(%6, 1))
β”‚   %8  = Core.getfield(%6, 2)::Int64
β”‚   %9  = Base.getindex(Main.garr, k)::Any
β”‚         Main.f(%9, 20)
β”‚         (@_2 = Base.iterate(%1, %8))
β”‚   %12 = (@_2 === nothing)::Bool
β”‚   %13 = Base.not_int(%12)::Bool
└──       goto #4 if not %13
3 ─       goto #2
4 β”„       return nothing

However, when I run this, this issue doesn’t happen:

function test1()
        f(g1, 20)
end
julia> @code_warntype test1()
Variables
  #self#::Core.Const(test1)

Body::Int64
1 ─ %1 = Main.f(Main.g1, 20)::Core.Const(400)
└──      return %1

Use a tuple?

1 Like

Keep getting the same problem:

garr = (g1, g2)
julia> @code_warntype test()
Variables
  #self#::Core.Const(test)
  @_2::Union{Nothing, Tuple{Int64, Int64}}
  k::Int64

Body::Nothing
1 ─ %1  = (1:2)::Core.Const(1:2)
β”‚         (@_2 = Base.iterate(%1))
β”‚   %3  = (@_2::Core.Const((1, 1)) === nothing)::Core.Const(false)
β”‚   %4  = Base.not_int(%3)::Core.Const(true)
└──       goto #4 if not %4
2 β”„ %6  = @_2::Tuple{Int64, Int64}::Tuple{Int64, Int64}
β”‚         (k = Core.getfield(%6, 1))
β”‚   %8  = Core.getfield(%6, 2)::Int64
β”‚   %9  = Base.getindex(Main.garr, k)::Any
β”‚         Main.f(%9, 20)
β”‚         (@_2 = Base.iterate(%1, %8))
β”‚   %12 = (@_2 === nothing)::Bool
β”‚   %13 = Base.not_int(%12)::Bool
└──       goto #4 if not %13
3 ─       goto #2
4 β”„       return nothing

The problem is that garr is a global variable that can change. You could pass it as an argument, or make it const garr = (g1, g2).

3 Likes

Each function has its own type, so I do not think that is possible in general. You will be in better shape if you can assert the return type of f, something like:

julia> function test2(garr)
           s = 0
           for i in eachindex(garr)
               s += f(garr[i],20)::Int
           end
           s
       end

julia> garr = [g1, g2];

julia> @btime test2($garr)
  29.114 ns (0 allocations: 0 bytes)
761

But if garr has more than a few types, that performance will probably break.