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 FunctionWrapper
s 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.