How does Julia dispatch on concrete methods?

Hi all,

I’m trying to understand how Julia figures out what functions to call when we implement an interface from say a library (or from some other module). That library has some functions that then are able to call the concrete implementations of the interface. I tried to model that in the following example but it doesn’t seem to work.

module World
module ModA
​
abstract type AbstractFoo end
struct Foo <: AbstractFoo end
​
function foo(x :: AbstractFoo)
    return length(x) + 1
end
​
function length(x :: Foo)
    return 10
end
​
end
​
​
module ModB
import ..ModA
​
struct Bar <: ModA.AbstractFoo end
​
function length(x :: Bar)
    return 42
end
​
end
end
​
World.ModA.foo(World.ModB.Bar())

Here ModA providces a concrete implementation of AbstractFoo, Foo. But say I want to make my own AbstractFoo so I implement Bar and the required method length in my ModB. Now I want to use the functionality foo, but I get an error.
Alternatively, I tried bringing everything into scope.

module World
module ModA
​
abstract type AbstractFoo end
struct Foo <: AbstractFoo end
​
function foo(x :: AbstractFoo)
    return length(x) + 1
end
​
function length(x :: Foo)
    return 10
end
​
export
    AbstractFoo,
    Foo,
    foo,
    length
end
​
​
using .ModA
​
Foo()
​
module ModB
using ..ModA
​
struct Bar <: AbstractFoo end
​
function length(x :: Bar)
    return 42
end
​
export
    Bar,
    length
end
​
using .ModB
foo(Bar())
​
end

This also doesn’t work. What is the reason that this doesn’t work? And how does this work for the libraries that are out there?

As an aside, I tried writing this without the World module surrounding both but importing the code from ModA was giving me some difficulties that I’m not entirely sure what caused them.

It doesn’t work because ModA.length and ModB.length are two different functions and ModA.foo calls ModA.length. To extend ModA.length in ModB instead of creating a new function ModB.length, you need to qualify it or import it explicitly, i.e., either

import ..ModA

function ModA.length(x::Bar)
    return 42
end

or

import ..ModA: length

function length(x::Bar)
    return 42
end

Edit: The table in this section of the manual shows what is brought into scope and what functions are made available to extend by using/import.

6 Likes

Thanks, I missed that there is such a qualifier as “method extension”. It is not defined on the page you linked to either. I wonder now how it works, does Julia keep a table of functions that go by the same name when defined with the “method extension” available? How does Julia know here to call ModB.length (it is not exported)?

Here is the working example

module World
module ModA

abstract type AbstractFoo end
struct Foo <: AbstractFoo end

function foo(x :: AbstractFoo)
    return println(length(x))
end

function length(x :: Foo)
    return 10
end

export
    AbstractFoo,
    Foo,
    foo    
end

module ModB
using ..ModA
import ..ModA: length

struct Bar <: AbstractFoo end

function length(x :: Bar)
    return 42
end

export
    Bar
end

using .ModA
using .ModB
foo(Foo())
foo(Bar())

end

Functions have an identity in Julia, and every method unambiguously belongs to a single function. When you do:

function foo(x)
  bar()
end

or, equivalently, foo(x) = bar()

then the following happens:

  • If you have already done import OtherPackage: foo in the current module, then a new method is added to the existing function OtherPackage.foo
  • Otherwise a new function is created in your current module and a single method is added to it.

You can always see the set of methods attached to a given function with methods(foo).

When you do: function OtherPackage.foo(...) or OtherPackage.foo(...) = ..., then you are unambiguously adding a new method to the existing function.

Your ModB is creating a brand-new function called “length”. This has nothing at all to do with ModA.length–the fact that they share a name is of no consequence whatsoever (this is completely different than C++, for example). You need to either explicitly import ModA: length or define the function directly as ModA.length. The latter is recommended because it makes the behavior obviously correct.

4 Likes