Manual dispatch over a constant type set

Ah, ok, that certainly makes things harder. If that weren’t a constraint in your design, you might consider something like the way RigidBodyDynamics.jl represents different types of robot joints with a generic Joint type which has a specialized JointType type parameter: https://github.com/JuliaRobotics/RigidBodyDynamics.jl/blob/0f42d2900174adc3c03911d217fd48a598c69bb8/src/joint.jl#L43-L47

As for your stream_dispatch, I think the idea is workable, but you might be better served by a lisp-y tuple recursion style, rather than a generated function. The basic idea is something like this:

julia> foo(x::Int) = println("got an int")
foo (generic function with 1 method)

julia> foo(x::Float64) = println("got a float")
foo (generic function with 2 methods)

julia> function call_for_each(f, args)
         f(first(args))  # Do something with the first element
         call_for_each(f, Base.tail(args))  # Recurse with the rest
       end
call_for_each (generic function with 1 method)

julia> function call_for_each(f, args::Tuple{})  
         # Base case for our recursion when nothing is left
       end
call_for_each (generic function with 2 methods)

julia> call_for_each(foo, (1, 2.0, 3, 4, 5.0))
got an int
got a float
got an int
got an int
got a float

This kind of approach is type-stable because the compiler is smart enough to figure out the type of the tail of a tuple:

julia> @code_warntype call_for_each(foo, (1, 2.0, 3, 4, 5.0))
Variables
  #self#::Core.Compiler.Const(call_for_each, false)
  f::Core.Compiler.Const(foo, false)
  args::Tuple{Int64,Float64,Int64,Int64,Float64}

Body::Nothing
1 ─ %1 = Main.first(args)::Int64
│        (f)(%1)
│   %3 = Base.tail::Core.Compiler.Const(Base.tail, false)
│   %4 = (%3)(args)::Tuple{Float64,Int64,Int64,Float64}
│   %5 = Main.call_for_each(f, %4)::Core.Compiler.Const(nothing, false)
└──      return %5

Note how the (%3)(args) has been inferred as a Tuple{Float64, Int64, Int64, Float64}, which is the type after the first element has been processed.

In your case, you have a tuple of types, and your foo() would be comparing the .subclass against each of those types, but I think the general idea could still work, and this completely avoids any need to worry about generated functions and the headaches they can create.

By the way, a totally different way to attack this problem would be to store FunctionWrappers from FunctionWrappers.jl. Those are just a slightly nicer wrapper around function pointers, and they can allow you to do something more like virtual methods in C++, although they come with the same set of performance issues that virtual functions in C++ have. I’ve done that in a few cases, although I generally find it not to be worthwhile.

3 Likes