Are local files/modules ever immediately importable with using
, or is include
always required?
For example, with a structure like this:
Project.toml
src/ # Source for a local library `MyLib`
MyLib.jl # contains `module MyLib`
SomeModule.jl # contains `module SomeModule`
SomeModule/
Foo.jl
Bar.jl
runnable_file_1.jl # these two rely on MyLib.jl
runnable_file_2.jl
Is all of the following necessary?
-
runnable_file_1
and 2
have include("src/MyLib.jl"); using MyLib
-
MyLib.jl
has include("SomeModule.jl"); export SomeModule
-
SomeModule.jl
has include("SomeModule/Foo.jl"); export Foo
and the same for bar
It seems to me that there might be a way for directory structure to create module structure without manually including then exporting everything - such that a top level runnable file could simply using MyLib.SomeModule.Foo
without needing the include
statement at all (ala Python with __init__.py
or Rust with pub mod
).
Is something simpler possible? Alternatively, is there a better structure for a local package than what I describe?
This question comes up a lot. The “best” way to do this is a contentious topic around here.
Personally I recommend FromFile, which does what you’re after.
(Others prefer some include
+ using
chicanery, which has a lot of boilerplate and suffers from a number of footguns. You can tell which side of this war I’m on.)
13 Likes
No, your scripts in runnable_file_?.jl
should merely do using MyLib
, and be executed with MyLib
being the “active” project. I.e in order to run runnable_file_1.jl
, you can
In the standard way of doing things, MyLib.jl
should definitely have include("SomeModule.jl")
. What else it has depends on what you want to do. Assuming SomeModule
exports a function foo
:
- if you do nothing:
MyLib
can refer to foo
as SomeModule.foo
runnable_file_1.jl
can refer to foo
as MyLib.SomeModule.foo
- if
MyLib
does using .SomeModule
(mind the .
in front of the submodule name):
MyLib
can refer to foo
as simply foo
runnable_file_1.jl
can refer to foo
as MyLib.foo
- if
MyLib
does export SomeModule
- it does not change anything for the code written in
MyLib.jl
runnable_file_1.jl
can refer to SomeModule
as simply SomeModule
(instead of MyLib.SomeModule
)
I’m not sure what “better” means, but I’ll say that a lot of packages only define the mandatory top-level module
(MyLib
in your example), but no submodule at all. Although most packages are sufficiently large that splitting their source code in multiple files, possibly grouped into subdirectories. Julia allows the two notions (file/directory structure on the one side, and module/submodule hierarchy on the other side) to be entirely decoupled, which allows package developers to adopt a lot of different structures.
Using FromFile
is perfectly fine. Another way of doing things, relying only on standard Julia features and provided that you don’t absolutely need to insulate the various parts of your project in different namespaces, would look like:
Project.toml
src/
MyLib.jl
feature1.jl
feature1/
foo.jl
far.jl
runnable_file_1.jl
with:
#file MyLib.jl
module MyLib
export a_user_facing_function # this export statement could have been placed somewhere else, for example right before the definition of `a_user_facing_function` in `feature1/foo.jl`
include("feature1.jl")
#file feature1.jl
include("feature1/foo.jl")
include("feature1/bar.jl")
#file feature1/foo.jl
a_user_facing_function(x) = 2*an_internal_function(x)
an_internal_function(x) = x + 1
#file runnable_file_1.jl
using MyLib
# can use a_user_facing_function directly because it was exported by MyLib
println(a_user_facing_function(42))
7 Likes
Another way is to start the runnable script with
using Pkg
Pkg.activate(@__DIR__)
Pkg.instantiate()
The third line is optional but means that dependencies will be automatically installed if they are missing, e.g. when running this the first time on another machine.
5 Likes
Interesting, FromFile
and the specification it mimics sounds like at least a syntax that’s easier to follow than the status quo. Thanks for the link!
Just for curiosity - was there ever a proposal for anything that builds the module tree off of paths, rather than needing the module Foo
within the included file? I’d personally be partial to something similar to rust - e.g. module SomeModule
/ export module SomeModule
creates a module from file SomeModule.jl
, and anything in a same-named folder could be used as submodules (if also declared). Kinda similar to Python, but without needing __init__.py
and with easier reexporting
1 Like
Thanks for the thorough response. Your example is kinda sorta what I’m currently doing, but it just seemed very clunky to need to use include
+ export
+ module
(with the same file name) to build project structure.
Julia allows the two notions (file/directory structure on the one side, and module/submodule hierarchy on the other side) to be entirely decoupled, which allows package developers to adopt a lot of different structures.
This is probably the context that I was missing, coming from languages that typically have them closely coupled. this page does sort mention that:
Files and file names are mostly unrelated to modules; modules are associated only with module expressions. One can have multiple files per module, and multiple modules per file.
But doesn’t talk about files/import again, and doesn’t clarify why it’s “mostly unrelated” and not completely unrelated.
Yes, while the currently available (or default) design patterns have served many Julia users well, I think they are a bit of a sore point for a sizeable chunk of other users. There are quite a few GH issues with a long & active debate on what exactly would be the best design moving forward. I am optimistic that __eventually__ there will be a resolution which will mean users write a lot fewer include(...)
s, but I wouldn’t hold your breath
1 Like
Well… I’m just glad to know I’m not the only one who struggles a bit I’m just a bit surprised these answers and the “best practices” are a bit tricky to track down.
1 Like
For clarity (in case this is what you were thinking), FromFile doesn’t need a module Foo
inside the include file.
Meanwhile as others have commented, the alternate approach to this usually involves creating just a single module
for the whole project, and then just include
ing everything into that one giant namespace. So this again doesn’t involve creating any extra modules inside files.
In terms of discussions: the main one on this topic is #4600.
3 Likes