Dispatching on module to prove ownership in package extensions

I was watching @kristoffer.carlsson’s JuliCon talk on Package Extensions and I was quite interested in the discussion about “proving” dependencies to ensure that a user explicitly loads the package for which there is a package extension.

The code snippet given in the talk is

function solve_system(A, x; alg::Module)
    ext_hypre = Base.get_exension(@__MODULE__ :HYPREExt)
    if ext_hypre !== nothing && alg == ext_hypre.HYPRE
        _solve_system(HYPRESolver(), A, x)
    elseif alg == ...
        # ...
    end
end

making users pass a module as alg to ensure that they’ve loaded the dependency explicitly.

I was wondering if there was a recommended pattern for generalizing this code to use multiple dispatch for choosing the solver. This would be to avoid the potentially long if-elseif-chain and to allow users to define their own optimization methods in their own (non-extension) modules.

The example snippet addresses the exact situation that I have in QuantumControl.jl (or actually, QuantumControlBase.jl) where currently, I’m dispatching on symbols for the optimization method, as

optimize(problem; method) = optimize(problem, method)  # keyword arg (public API)
optimize(problem, method::Symbol) = optimize(problem, Val(method))

and then e.g. in Krotov.jl, I have

optimize(problem, method::Val{:krotov}) = optimize_krotov(problem)

I might want to move that definition from Krotov.jl to an extension module of QuantumControl.jl so that users don’t necessarily have to install all the packages for the various methods to use QuantumControl.jl with just one method. At the same time, I’d like to keep the ability for someone to have their own method in a package I don’t know anything about, and extend optimize in a consistent fashion.

Unfortunately, I don’t think there’s any way to directly dispatch on a module, since typeof(SomeModule) is Module. It would have been nicer if typeof(SomeModule) was a singleton sub-type of Module (just like typeof(some_function) is a subtype of Function), but without that, it seems to me the easiest way to implement it is to simply add

optimize(problem, method::Module) = optimize(problem, Val(nameof(method)))

That is, dispatch on the name of the module as a symbol.

Are there any better way to more directly dispatch on a Module?

2 Likes

In Julia 1.10+ you can have a module as a type parameter and do something like (using the same example as in the talk):

julia> module HYPRE end;

julia> module Pardiso end;

julia> struct Algorithm{T} end

julia> f(::Algorithm{HYPRE}) = "HYPRE"
f (generic function with 1 method)

julia> f(::Algorithm{Pardiso}) = "Pardiso"
f (generic function with 2 methods)

julia> f(Algorithm{HYPRE}())
"HYPRE"

julia> f(Algorithm{Pardiso}())
"Pardiso"

That might be one way to use dispatch for it.

3 Likes

Will it also be possible to dispatch by the module itself, as in f(::typeof(HYPRE)) = ... ?

I don’t think so, same way you cannot dispatch on f(::typeof(:symbol)).

Dispatch is possible by structs, types, functions — why not for modules?..
Well, at least Val(MyModule) will work, right?

As of 1.91.10, I believe so. Allow Module as type parameters by Keno · Pull Request #47749 · JuliaLang/julia · GitHub is the PR which enabled this.

Edit: missed Kristoffer’s comment above.

Well, at least Val(MyModule) will work, right?

As of 1.9, I believe so. Allow Module as type parameters by Keno · Pull Request #47749 · JuliaLang/julia · GitHub is the PR which enabled this.

Unfortunately, I don’t think that made it into 1.9. In fact, I’m pretty sure that PR is exactly what enables “In Julia 1.10+ you can have a module as a type parameter”. Definitely something to look forward to in the next version!

1 Like

Well, not functions, just the type of functions. Neither functions nor modules are types. You can define a method with the signature f(::typeof(:symbol)) or f(::typeof(HYPRE)), that just evaluates to f(::Symbol) and f(::Module). If you mean something like ::Type{T} to narrow down a type’s type, that’s just type parameters.