Is there a way to "forward" getindex for Tuples in a type-stable way?

Don’t use getindex with runtime integers i (x[i]) if you want it to be type-stable. Instead, you need all of the those integers to be known at compile-time. So

for i = 1:3
  x[i]
end

are not type stable calls for an inhomogenous tuple, while

x[1]; x[2]; x[3]

is. Two ways to handle this. One is a metaprogramming: generated functions work nicely. But another way is recursion. I’m thinking of writing a blog post to describe it in full, but an example of how to do this is found here:

https://github.com/JuliaDiffEq/OrdinaryDiffEq.jl/blob/master/src/callbacks.jl#L195

In this example, apply_discrete_callback!(integrator,callback) needs to be called on every callback in a tuple (and each callback is differently typed). So the basic call is:

apply_discrete_callback!(integrator,discrete_callbacks...)

This then gets “caught” by function headers like

apply_discrete_callback!(integrator::ODEIntegrator,callback::DiscreteCallback,args...)

where callback is the first callback in the list and args is the rest. You can then recrusively do something on callback and pass args into apply_discrete_callback!, and it’ll get smaller an smaller. Manage the Base cases and you’re good: no getindex was ever needed.

Then, for this code, the compiler will inline this into one giant call. It essentially makes it amount to:

apply_discrete_callback!(integrator,callback[1])
apply_discrete_callback!(integrator,callback[2])
apply_discrete_callback!(integrator,callback[3])
...

knowing what the index is at compile time and thus able to infer each call and be type-stable. The only cost here is the compile time, but if you’re keeping the tuples around and repeatedly calling this, it’ll only have to compile this full recursive routine once for the the tuple.

1 Like