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.
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 functionOtherPackage.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.