Maybe this is the important point to resolve? As far as I can tell, FunctionWrappers.jl is the exact right solution to your original problem. For example:
julia> using FunctionWrappers: FunctionWrapper
julia> ops = [+, *]
2-element Vector{Function}:
+ (generic function with 190 methods)
* (generic function with 328 methods)
julia> wrapped_ops = [FunctionWrapper{Int, Tuple{Int, Int}}(op) for op in ops]
2-element Vector{FunctionWrapper{Int64, Tuple{Int64, Int64}}}:
FunctionWrapper{Int64, Tuple{Int64, Int64}}(Ptr{Nothing} @0x00007fe24935fed0, Ptr{Nothing} @0x00007fe297964a78, Base.RefValue{typeof(+)}(+), typeof(+))
FunctionWrapper{Int64, Tuple{Int64, Int64}}(Ptr{Nothing} @0x00007fe249360190, Ptr{Nothing} @0x00007fe297964a80, Base.RefValue{typeof(*)}(*), typeof(*))
julia> using BenchmarkTools
julia> @btime (for i in 1:2; z[i] = $ops[i](x, y); end) setup=(x = 1; y = 2; z = zeros(Int, 2))
60.315 ns (0 allocations: 0 bytes)
julia> @btime (for i in 1:2; z[i] = $wrapped_ops[i](x, y); end) setup=(x = 1; y = 2; z = zeros(Int, 2))
12.344 ns (0 allocations: 0 bytes)
wrapped_ops
is a concretely-typed Vector
of function wrappers, so we can iterate over it and called the wrapped operations much more efficiently.
If you find the FunctionWrapper
syntax awkward, I wrote a macro here: Can FunctionWrappers.jl express higher order functions? - #4 by rdeits which might help:
wrapped_ops = [@fn((Int, Int) -> Int)(op) for op in ops]
or, equivalently:
wrapped_ops = @fn((Int, Int) -> Int).(ops)