Generic functions sourced from multiple modules?


#1
module A
 type type1
 foo(a::type1)
end

module B
 type type2
 foo(a::type2)
end

if you do that and then try

using A
using B
foo(arg)

WARNING: both B and A export “foo”; uses of it in module Main must be qualified
ERROR: LoadError: UndefVarError: foo not defined

The obvious way around this is to put those definitions in a single module and export foo, however the goal is to keep all the functions segregated by type.

i’ve looked through the methods and modules section of the docs and haven’t seen a way around this.

I even poked around in the julia source code, and it looks like the solution is, in fact, to make all the definitions in one file and then export from there, i.e. the modules are organized by method as opposed to type.

Just making sure before I start organizing my source code.

Thank you.


#2

The best way around this for now is to have a third module that defines the common interface.

This is how, for example, StatsBase works. It defines a bunch of common names for statistical models and even gives them documentation, but it doesn’t actually define any concrete implementations. Other packages import these functions from StatsBase and extend them with their own implementations. This defines a common vocabulary and ensures that all the functions are defined in a consistent manner.


#3

It’s also how MathProgBase, DiffEqBase, DiffBase, etc. work. It’s a very common design pattern for large multi-package ecosystems in Julia, and it works quite well. I would also recommend having a Base package which defines the abstract types and generic functions.


#4

So i would need an abstract type which covers type1, type2, … typeN

Each of A,B,… would then subtype that abstract type.

but you say “and generic functions”. so each of my definitions of foo would need to go in the base file and could not be distributed in each individual file ?

So base.jl would contain

function foo(obj::type1)
function foo(obj::type2)
etc…

?

Sorry, I’m being a bit dense. The packages you’ve listed are quite involved and i’m having trouble distilling them down to what precisely I would do for my simple example.


#5

I would say you should be doing that in the first place. Typing of functions is only needed for dispatch, and abstract types will do just as well and be more generic. Of course, you could do without the abstract types if you want, it depends on whether you need them for dispatch.

No, just define in MyBasePackage function foo end. Then in the other packages, import MyBasePackage: foo. Then you add methods, all to the same function foo. It’ll work out correctly this way.


#7

Thanks for the help.
I thought I would post my complete example here just so that future readers might see exactly what I did and also to make sure that i’ve got the right idea.

module C
abstract Base
function foo end
end

module A
export foo, Atype
import C:foo, Base
type Atype <: Base
    a :: String
end
function foo(a::Atype)
    println("string:",a)
end
end

module B
export foo, Btype
import C:foo, Base
type Btype <: Base
    a :: Int
end
function foo(a::Btype)
    println("int:",a)
end
end

test code

using A
using B

foo(Btype(1))
foo(Atype("string"))