This technically already happens, just not in the way you think it can.
First, we have to throw out the U
part because a specific call signature with a unique piece of compiled code may not have a fixed return type, so it’s not a useful part; you could make U
abstract and leave it entirely up to the compiler, but that’s not controllable or stable. Second, 1 method (and method signature) can have an infinite number of call signatures (and thus compiled specializations) at once e.g. foo(x::Real)
can be compiled for foo(::Int)
and foo(::Float64)
. Type stability and control makes call signatures (like this very package!) better to specify, so you wouldn’t in fact want to dispatch on methods in any sense. Even in your example, T=Float64
, and you make it clear no method fn(::Float64)
exists, only fn(::Real)
. I see what you want, but the type system does not allow Blah{Float64}
and Blah{Real}
to share an instance. It also doesn’t allow multiple supertypes; T=Int
in the example calls in fact, so sin
, sum
, etc need to subtype Function{Int}
as well.
The good news is you don’t need any of that stuff. The first hint is that when a function call is dispatched, the types of the callable and the arguments are taken into account, so the types of functions like sin
and sum
only have to care about the callable. That’s how we can write methods for callable instances and vary positional arguments in multimethods, yet let the compiler infer specific compiled code so often.
So if we can already dispatch on all parts of a call, why the concept of FunctionWrapper
s in the first place? Sometimes we can’t narrow down a call site to 1 specialization because one position, often the callable, has to change types at runtime, and that normally needs possibly costly dynamic dispatch and ruins type inference downstream. So FunctionWrappers.jl wrapped specializations with input/output type conversions, putting that information in a concrete type (and parameters) so we can lump many callables together. EnforcedTypeSignatureCallables won’t be able to elide the dynamic dispatch part because most different functions will be represented by an abstract type parameter, but it does provide more inferrability and it’s not subject to many of FunctionWrappers’ constraints and dynamism shortcoming (example).