I have problems on importing struct from other files. Suppose I have the file structure:
Lattices.jl defines a struct called
Hamiltonian on a
main.jl tries to create a
a = Lattice(16, 16)
ham = Hamiltonian(a, [1.0, 0.0])
However, I get the error
ERROR: LoadError: MethodError: Cannot `convert` an object of type Lattice to an object of type Main.Hamiltonians.Lattices.Lattice
So the problem is that when I import
Lattice into the
Hamiltonians module, its “scope” is also changed (sorry if I am not using the correct terminology; I come from Python so I have never encountered such behavior before). It is no longer regarded as the same
Lattices.jl. How do I tell Julia that different
Lattice’s are the same thing?
You should not
include("Lattices.jl") inside “Hamiltonians”, because
include is like copying-and-pasting the code there. That means that you are defining a completely independent
Lattices module inside
What you want there is to use:
using ..Lattices # two dots
to load the module you defined before.
(the takeaway is: never
include the same file twice).
Thanks! Actually I never understand the dots in front of the module names. Could you please further explain? In addition, if
Lattices.jl is in a folder
lattices/Lattices.jl, the two-dot import will not work; how to deal with this case?
The dots indicate relative module paths. When you import a package, you don’t have dots because packages are stored distantly; the lack of dots informs the import statement to look elsewhere. In this case, you are
includeing a module directly into your module tree (
Main containing modules that can contain other modules). To share names among the modules, you do dotted imports, the dots helping the import statement walk along the tree. So that’s why you don’t want to
include twice, you’re evaluating a copy, a separate branch so to speak.
tbh, the dots still gets me with off-by-one errors, but hopefully this contrived example helps:
julia> module Foo
module Bar0 end
module Bar2 end
using ...Foo # Bar1 -> Foo -> Main.Foo
using ..Foo # Bar1 -> Foo.Foo (watch out, modules contain own name)
using ..Bar0 # Bar1 -> Foo.Bar0
using .Bar2 # Bar1.Bar2
One dot is a starting point, it just means you look in the global scope that the import statement is in. With each additional dot, you go up a global scope to look. With enough dots you reach
Main, and more dots won’t go anywhere.
The dots are necessary. You can’t qualify the name of the searched scope like
relativeimport Foo.Bar0 because two separate modules can share the name
Foo in different branches of the module tree. If it helps you look up a file faster, you could just comment the name like the above and use the dots to tell synonyms apart sometimes; I personally don’t nest modules deeply so the walk isn’t far. The
Bar0 part on the other hand must be named to select one out of possibly many modules in the searched scope.
include command will change, but not the
using ..Lattices (in that case).
That said, the most common approach is to just have one “big module”, lets say, called “MyQuantumPackage”, and just include the source of the sub-functionalities in that module, without splitting them into different modules each (which will make your like harder). Something like:
Lattices.jl is just:
(without the module), and the same for
Hamiltonian, such that you don’t need to import names from other modules all the time within your package.
In other words, split into modules if you need separate namespaces that share select names via imports. If you don’t need such encapsulation, it’s easier to work in 1 namespace, and
include can evaluate multiple source files into it, which helps prevent a big namespace from making too big a file.
@Benny in your example, what is the difference between Main.Foo and Foo?
Foo are names of 2 modules. Modules contain their own names by default and the names of nested modules, so
Main.Foo access the same module. I was warning you of that because it might be confusing why
using ..Foo and
using ...Foo both work; you’re just searching the name
Foo in different modules.
I recommend avoiding
Foo.Foo so the same number of dots reach modules on the same level;
Foo isn’t on the level of
Bar0 or any other module it may contain. It’ll also cover the pathological cases where a module’s own contained name is reassigned to another module:
julia> module Foo
x = "outer"
module Foo # don't repeat names in practice
x = :inner
using ..Foo # Bar -> Foo.Foo
WARNING: replacing module Foo.
julia> Foo, Foo.Foo