Is it possible to get the most fitting dispatch types?

Say that I have a list of function argument types

possible_argument_types = [(Int,), (Int,Union{Real,Char}), (Float64,Int), (Any,Int)]

And the input type

input_type = (String,Int)

Is it possible for a function like this to work?

would_dispatch_to(input_type, possible_argument_types)
# returns (Any,Int)

I don’t know where to look for this functionallity in the internals of Julia.


An alternative option that works for my problem would be to allow dispatch with a list of anonymous functions

f1 = (a::Int) -> ...
f2 = (a::Int,b::Union{Real,Char}) -> ...
f3 = (a::Float64,b::Int) -> ... 
f4 = (a,b::Int) -> ...
would_dispatch_to(input_type, [f1,f2,f3,f4])
# returns f4

Not knowing the context, this may not be helpful, but: Since you mention dispatch, I infer that you are trying to find out which method (signature) is called for a given input combination. @which may help:

julia> foo(x :: Union{Float64,Int}) = 1
foo (generic function with 1 method)

julia> foo(x :: String) = 2
foo (generic function with 2 methods)

julia> meth = @which foo(1)
foo(x::Union{Float64, Int64}) in Main at REPL[2]:1

julia> meth.sig
Tuple{typeof(foo), Union{Float64, Int64}}

Another interesting method to learn about the actual dispatch is to use the VSCode debugger.

You can type:

@enter fun(x, y, z)

in the VSCode repl and it will follow you through the exact execution path.

Thanks for the responses, I think i didn’t make myself clear enough. So i’ll give some extra information.

What i’m looking for is to do “rule-based transformation”. I want to transform the input to some output dependent on some rules. Think of f1… f4 in the seocnd part of the question as the rules.

The problem is that I also want to be able to add and remove rules based on user input. Thus dispatch with a function is not enough as removing rules wouldn’t be possible then.
A solution to any of the 2 questions would allow me to build such a system.

Simply finding the first type signature that matches is fairly straightforward:

types_match(input, possible) = all(input .<: possible)
types_match(input) = possible -> types_match(input, possible)

function would_dispatch_to(input, possibles)
    i = findfirst(types_match(input), possibles)
    i ≡ nothing && return nothing
    return possibles[i]
end

Assuming your use-case is fairly simple this should be fine but the actual dispatch algorithm that Julia uses is more complicated than this. In particular, the way that you build your possible_argument_types is crucial, as well as detecting when you have ambiguous/overlapping definitions.

1 Like

For future reference, I got it to work AFAIK starting from your solution. This one accounts for multiple possible matches.

types_match(input, possible) = all(input .<: possible)
function would_dispatch_to(input_sig, d::Set)
    input_sig in d && return input_sig
    most_specific = nothing
    equaly_specific = Any[]
    for sig in d
        if types_match(input_sig, sig)
            if most_specific === nothing
                most_specific = sig
            else
                if types_match(sig, most_specific)
                    most_specific = sig
                    empty!(equaly_specific)
                elseif !types_match(most_specific, sig) #test for ambiguity
                    push!(equaly_specific, sig)
                end
            end
        end
    end
    length(equaly_specific) > 0 && error("MethodError: $input_sig is ambiguous. The candidates are:\n\t$(mapreduce(identity, (a,b)-> (string(a)*"\n\t"*string(b)), (most_specific, equaly_specific...)))")
    most_specific === nothing && error("MethodError: no method matching $input_sig")
    most_specific
end

possible_argument_types = Set([(Int,), (Int,Union{Real,Char}), (Float64,Int), (Any,Int)])
input_type = (String,Int)
would_dispatch_to(input_type, possible_argument_types)