[ANN] MultipleInterfaces.jl: Interfaces with multiple inheritance and multiple dispatch

I’m pleased to announce the release of MultipleInterfaces.jl. MultipleInterfaces.jl provides a powerful way to define and work with interfaces in Julia. With MultipleInterfaces.jl you can declare an interface that is defined by a list of required methods, and you can declare which types implement that interface. Interfaces support multiple inheritance, interface intersection, and multiple dispatch. And all with no runtime cost.

MultipleInterfaces.jl is still in the General Registry 3-day waiting period, so for now you can install via the Github repo, like this (in the Pkg REPL):

pkg> add https://github.com/CameronBieganek/MultipleInterfaces.jl.git

Here’s a quick example of how to use MultipleInterfaces.jl. For more details, see the README.

using MultipleInterfaces

function a end
function b end
function c end
function d end

@interface A begin
    a
end

@interface B begin
    b
end

@interface C extends A, B begin
    c
end

@interface D begin
    d
end

struct Ant end
struct Bear end
struct Cat end
struct Dog end
struct Mouse end

@type Ant implements A
@type Bear implements B
@type Cat implements C
@type Dog implements D
@type Mouse implements B, D

@idispatch foo(x::Int, y: A, z: D) = 1
@idispatch foo(x::Int, y: B, z: D) = 2
@idispatch foo(x::Int, y: C, z: D) = 3
@idispatch foo(x::Int, y: A, z: B & D) = 4
julia> foo(42, Ant(), Dog())
1

julia> foo(42, Bear(), Dog())
2

julia> foo(42, Cat(), Dog())
3

julia> foo(42, Ant(), Mouse())
4

JuliaCon 2025

Come see my talk about MultipleInterfaces.jl at JuliaCon!

To Do:

  • Add to the documentation and create a Documenter.jl website.
  • Fix some known bugs related to supporting various function definition syntaxes in the @idispatch macro.
33 Likes

Interesting! Could you say a few words how this compares to other similar packages such as GitHub - Seelengrab/RequiredInterfaces.jl: A small package for providing the minimal required method surface of a Julia API or GitHub - mrufsvold/DuckDispatch.jl: If it quacks like a duck... dispatch on it! ?

2 Likes

I’m not an expert on the other packages, but I’ll try to summarize the differences as I see them. First I’ll start with more details about the approach and philosophy of MultipleInterfaces.jl.

MultipleInterfaces.jl

MultipleInterfaces.jl has two main goals:

  • Provide a simple and ergonomic way to define a DAG of related interfaces, via multiple inheritance of interfaces.
  • Allow multiple dispatch on those interfaces.

And a secondary goal:

  • Provide interface intersections that can be dispatched on, like Foo & Bar.

MultipleInterfaces.jl claims to be about interfaces, but in reality it’s an alternative type system with abstract multiple inheritance.

Although MultipleInterfaces.jl provides an alternative type system, the spirit of the type system (“interfaces”) in MultipleInterfaces.jl is very similar to the spirit of the Julia type system:

  • It’s a nominal type system.
  • All interfaces are abstract. Concrete types are final.
  • In analogy to abstract types in Julia, interfaces do not enforce anything about their implementors. They are only used for dispatch.

One of the original inspirations for MultipleInterfaces.jl was the About Interfaces section of @Sukera’s RequiredInterfaces.jl documentation, which, if I recall correctly, discusses multiple inheritance and proposes that interfaces should not contain optional methods. Multiple inheritance provides more flexibility in composing different interfaces, so if we have multiple inheritance we shouldn’t really need “optional” interface methods anymore.

I was also inspired by Haskell type classes, Rust traits, and Java interfaces, all of which support abstract multiple inheritance. I want to be able to define a DAG of interfaces similar to this DAG of type classes in Haskell:

Base-classes

(Image from the Haskell wikipedia page.)

As mentioned, MultipleInterfaces.jl does not enforce any requirements on types that declare that they implement an interface. I recommend that developers who define interfaces also create a unit test suite to test objects that implement that interface, possibly with the help of Supposition.jl.

RequiredInterfaces.jl

RequiredInterfaces.jl seems to be focused mainly on ensuring that an interface implementation has implemented all the methods that are required by the interface. It uses regular abstract types as the flags for an implicit interface, which means that there is no mechanism for abstract multiple inheritance.

Interfaces.jl

Interfaces.jl seems to be focused on testing that an interface has been properly implemented. It allows interfaces to have optional methods. That makes sense in the context of the current Julia ecosystem, but I think optional methods cause a lot of headaches.

Dispatching on interfaces is not really provided by Interfaces.jl. I think Interfaces.jl provides some kind of trait object that can be used in a Holy-trait style, but it’s not well documented. And there is currently no support for multiple inheritance of interfaces. (Actually, I don’t see support for single inheritance of interfaces, either.)

DuckDispatch.jl

DuckDispatch.jl might be the closest to MultipleInterfaces.jl. However, there are some differences:

  • It uses a structural type system (inspired by Go) rather than a nominal type system.
  • Based on the README, it looks like DuckDispatch.jl might impose more requirements on the required methods, whereas MultipleInterfaces.jl takes the traditional Julia approach of relying on docstrings and unit tests to define the expected behavior of the interface.
  • I can’t tell if DuckDispatch.jl supports multiple inheritance of DuckTypes.

Perhaps @mrufsvold can comment more on the details of DuckDispatch.jl and how it differs from MultipleInterfaces.jl.

8 Likes

First of all, congratulations on the release! As the author of RequiredInterfaces.jl, maybe I can also add something :slight_smile:

Conceptually, this seems very close to where I would have wanted to go with RequiredInterfaces.jl, but as you correctly point out, multiple interface inheritance just doesn’t work in the current julia type system if the interface is based on subtyping an abstract type. That’s also the reason it has stagnated a bit. MultipleInterfaces.jl solves this problem by explicitly managing the additional “subtypes” itself, but this of course comes at the cost of having to use @idispatch for defining new methods! If this sort of interface inheritance were supported by the core type system, this would of course not be needed.

One question I do have is how you’re dealing with conflicting interfaces that both require implementing a certain method/signature?

I’m very curious how this package will develop and how the community adopts it :slight_smile:

4 Likes

For the sake of completeness, or to inspire further design decisions, there was a prototype of multiple inheritance in Interfaces.jl: Multiple Inheritance by rafaqz · Pull Request #43 · rafaqz/Interfaces.jl · GitHub

3 Likes

From a technical point of view, there is no issue, since the list of required methods for an interface is not used at all in dispatch. The list of required methods is just there to encourage the idea that an interface is a fixed list of required methods, and to make that list easy to query via required_methods and all_required_methods.

From a conceptual point of view, I hope that commonly used methods can live in small interfaces at the top of the interface hierarchy and be inherited from directly or indirectly by more specific interfaces farther down in the hierarchy. Designing the right hierarchy can definitely be challenging, but that’s the fun part. :slight_smile:

Regarding method signatures, I think as a general design principle, there should be only one docstring per function arity. So, packages that extend that function should only do so by adding specializations that conform to the generic definition.

Not sure if that fully answers your question or not…

2 Likes