How to find if a method is implemented for an interface?

Suppose I have a simple interface for which some methods are optional:

abstract Foo{T}
method1{T}(::Foo{T}, x, y)
method2{T}(::Foo{T}, a, b) # optional

The user of the interface should implement method1, but the implementation of method2 is optional.

By the time I am using the user type derived from Foo{T}, I need to know if the method2 is available. How can I do this in Julia?

1 Like

Does method2 have a fallback method that would be called in the case no special implementation is provided for the type? If not you could try using method_exists.

2 Likes

You may prefer the function applicable(method2, instance, a, b), both functions found in Generic Functions.

3 Likes

Thank you @Evizero, this looks like a good starting point.

Thank you @akis for the addition, also very helpful.

method_exists and applicable may work depending on the use case. But note that checking method_exists happens at runtime, which may not be what you want. Here’s a more complicated but possibly more performant option (depending on whether compile-time enhancements out-weigh the cost of compilation) by mixing method_exists with traits.

In JuliaDiffEq, our interface for providing extra functions is by adding additional dispatches. Because this was very important to us, we came up with a way to check for the existence of dispatches that will actually work at compile time (i.e. if you check the @code_llvm, the function has_jac(f) (checks if the Jacobian dispatch exists) will compile down to a true/false, and in codes which use if has_jac(f) the inappropriate branch will compile away and no checking needs to be done at runtime).

You can see the PR with how we came about the solution here:

It makes use of @mauro3 's SimpleTraits.jl. Let me spell it out in more detail since it’s a little tricky.

@mauro3 provided the code which uses method_exists to see if a method exists by checking the first arg for a certain type (you can see in the docs that our interface has a value-type in front for each “extra dispatch”)

check_first_arg(f,T::Type) = check_first_arg(typeof(f),T)
function check_first_arg{F}(::Type{F}, T::Type)
    typ = Tuple{Any, T, Vararg}
    for m in Base.MethodList(F.name.mt) # F.name.mt gets the method-table
        m.sig<:typ && return true
    end
    return false
end

However, if you @code_llvm that, you’ll see that (because I think the MethodList is global?) that the code is huge and it actually is not as fast as you’d hope. So then we use an internal function:

__has_jac(f) = check_first_arg(f, Val{:jac})

to check for the dispatch, and use that internal function to apply a trait:

@traitdef HasJac{F}
@generated SimpleTraits.trait{F}(::Type{HasJac{F}}) = __has_jac(F) ? :(HasJac{F}) : :(Not{HasJac{F}})

Since checking for traits is a compile-time check, the following is then what we really wanted:

has_jac{T}(f::T)   = istrait(HasJac{T})

In the end, @code_llvm compiles has_jac(f) to either be a true or a false, and everything works at compile time.

Of course, this can be major overkill depending on your use case. However, we found this solution useful since it allows us to have different branches (and dispatches due to traits) which fully optimize and are type-stable thanks to the fact that the existence of the method ends up as compile-time information.

5 Likes

That’s a nice explanation, thanks Chris.
Note that now there is macro-sugar in SimpleTraits.jl for above pattern:

@traitdef HasJac{F}
@traitimpl HasJac{F} <- __has_jac(F)

the second line generates: @generated SimpleTraits.trait{F}(::Type{HasJac{F}}) = __has_jac(F) ? :(HasJac{F}) : :(Not{HasJac{F}}). This works for any function some_check(T)::Bool.

just some random thoughts, not comprehensive answer.

one usual solution i keep seeing in julia source, is creating a default for the abstract type, and either giving some fallback behavior, or just returning “nothing”.

another usual solution is to give info on the actual type, for example iteratorsize which gives back enum values HasLength() | HasShape() | IsInfinite() | SizeUnknown(). this way an implementor can communicate attributes of a subtype back to the common code. the common code decides actions based on this value.

One reading of the original question is whether the specific method2 for a subtype of Foo (e.g., Bar) exists, as opposed to a method for Foo or an intermediate abstract type. This may be tested with

:method2 in [m.name for m in methodswith(Bar)]

Other replies seem to accept the more general methods, and probably they fit your needs, but perhaps this expression will help anyone who (mis-)reads the question as I did. The generality applies to method_exists and applicable, but I don’t understand how type hierarchy interacts with traits.

While I am at it, I will express the hope that @ChrisRackauckas will find the time to write a blog post about traits, since (a) he is one of the few package writers using them, (b) he writes good blog posts, and (c) his real-world use cases may guide the developers to optimize the implementation of traits in the language proper.

1 Like

For the sake of anyone who reads this, we are pursuing a method_exists-based scheme with some macros to reduce boilerplate code. Our solvers will check if the user’s POMDP type has the required functions like this:

function POMDPs.solve{S,A,O}(s::CoolSolver, p::POMDP{S,A,O})

    # register requirements
    @check_requirements "CoolSolver" begin
        PType = typeof(p)
        @req discount(::PType)
        @req states(::PType)
        @req actions(::PType)
        @req transition(::PType, ::S, ::A)
        s = first(states(p))
        a = first(actions(p))
        t_dist = transition(p, s, a)
        @req rand( ::AbstractRNG, ::typeof(t_dist))
    end

    # do the work of solving the pomdp
    # ...
end

which will output something like this if rand is missing

For more discussion, see

3 Likes