Julia’s compiler has gotten so good, that it’s almost never needed to use generated functions for performance, besides working around compiler edge cases.
If you hit such a compiler edge case, you should first create a minimal working example (MWE) that clearly shows why you need a generated functions to get the performance you want.
If you don’t have such a clear cut example, I’d strongly recommend avoiding generated functions.
I’m not sure if you really want to create a new type like that, it sounds like a bad idea (Julia already suffers a lot from slow compilation, and this will just make it much worse).
But if you really must create a new type for type inference, you could do something like this:
struct Wrapper{Data, ID}
data::Data
end
Base.getproperty(x::MyType, field::Symbol) = getfield(getfield(x, :data), field)
Base.setgetproperty!(x::MyType, field::Symbol, val) = setfield!(getfield(x, :data), field, val)
MyType{ID}(data::Data) where {ID, Data} = MyType{Data, ID}(data)
So you can just increase the ID in the generated function to invoke new specialization.
Btw, if I were you, I’d put a considerable amount of time into benchmarking, designing, creating minimal examples etc…, to really make sure that you need to go down the route you are.
These kind of patterns are almost guaranteed to break with new Julia versions and very hard to maintain, debug etc. So if there is an alternative way that doesn’t rely on Core.Compiler.return_type
, eval
and generated
functions, it should be greatly preferred, even if it may end up with a bit more code or a less magical API.
This has been proven true over and over again in Julia Base and other low level packages
You cannot do this statically. Jameson said that isapplicable
might become inferable sometime, then you could do it, even without generated functions.
If you really need to specialize on the availability of functions and do stuff in the type domain that need generated functions, I recommend isolating those cases to some small basic functions.
E.g. do:
# pure may be enough, if you already do fishy stuff ;) If not, you could also use a generated function here.
# of course this is obviously not pure, since isapplicable can change with every call
# but I guess you will be fine to have this less dynamic for increased performance
Base.@pure static_isapplicable(f, args...) = isapplicable(f, args...)
function myfunc(f, args...)
if static_isapplicable(f, args...)
....
else
...
end
end
Instead of:
@generated function myfunc(f, args...)
if isapplicable(f, args...) # doesn't work for types, but lets keep the example simple
return quote ... end
else
return quote ... end
end
end
This way it becomes more isolated and a bit easier to maintain - and if e.g. Base.isapplicable becomes inferrable, you can just swap those out. You can also move stuff into the type domain by returning Val(SomeConstant)
, which you could create in a generated function, and then you’re able to use those results in normal functions, while being able to fully specialize on it