Best practice for managing package hierarchy

What’s the best practice for the management of a package and its modules?
Please take a look at the following example.
In this example, module A is a kind of a Base module, and module B is a corresponding module.
Also, I’d like to make functions accessible as MyPackage.my_func (without exporting my_func like using Reexport).

What should I do?

  • src/MyPackage.jl
module MyPackage
    # `include("A.jl"); using .A` does not make support `MyPackage.A`.
end
  • src/A.jl
module A
    abstract type AbstractA end
    function my_func(a::AbstractA)
        # do something
    end
end
  • src/B.jl
module B
    # how to use AbstractA here?
end

Now I’m taking the following way but I think it’s too confusing.

  • src/MyPackage.jl
module MyPackage
    export AbstractA
    abstract type AbstractA end
    include("A.jl")
    @reexport using .A
    using .A: my_func  # to make not exported functions accessible as `MyPackage.my_func`
    include("B.jl")
    @reexport using .B
end
  • src/A.jl
module A
    using MyPackage: AbstractA
    # ...
end
  • src/B.jl
module B
    using MyPackage: AbstractA
    # ...
end

Why would you constrain your namespace so much? I mostly have one module where I do include various *.jl files. This is sufficient even for medium size libraries.

One alternative is to organize your module into submodules as described in Submodules and relative paths in the Julia docs. That could give you a cleaner and easier organization.

3 Likes

Sorry but I didn’t understand.
Probably I’ve not figured out how to manage Julia packages.

If you don’t mind, can you give me an example in detail?
It would be much better if you apply your way to the example I wrote.

+) I prefer to manage a package by writing files for each module.
Your suggestion may be writing a whole library within a file and employing submodule-relative path methodology. Right?

ok, like you I would have

  • src/MyPackage.jl, then
module MyPackage

abstract type AbstractA end

include("A.jl")
include("B.jl")
...

export AbstractA, my_func, my_other_func

end
  • src/A.jl does not need to be another module, just gets included into MyPackage. Since AbstractA was declared before A.jl got included, you can use it:
# src/A.jl
function my_func(a::AbstractA) 
    # do something
end
  • the same goes for src/B.jl (you don’t need another module):
# src/B.jl
function my_other_func(a::AbstractA)
    # do something else
end

If you write include above, the compiler sees MyPackage.jl like one larger source file, but you have the convenience to organise your functionality into several files. Just take care that types like your AbstractA are declared before being used in functions or other type declarations.

I usually have one source file like types.jl and include it first, where I declare common types, used by other source files included after that. Thus I do not need to declare types in my main module file like MyPackage.jl.

Does that make more sense now for you?

3 Likes

Oh, I didn’t know that declaring AbstractA before include("A.jl") makes it possible to use the type in A.jl.
It’s wonderful! Exactly I’ve found it and spent long time to understand the management of Julia package.

Also, I’ve taken a look at packages you’ve written. It makes everything clear. Thanks a lot @pbayer !

EDIT: Then, it would be very important to place include("some_files.jl") in an appropriate order.
EDIT2: For other readers who are confused like me, I would say that include("my_file.jl") will work as if the code written in my_file.jl is at the place where the include is.

1 Like

Glad to help!

I also learned a lot about code organization by looking at other libraries. For example I can recommend very much to look at:

The Julia source code itself is so huge, it can be overwhelming at first.

2 Likes

hmmmmm… almost. The included file is evaluated at the global scope of the module calling it. So, if the code included declares a variable myVar, and you call include(....) within for example a for loop or a function, myVar would be in the global scope of the module, not in the local scope of the for block or of the function.

1 Like