I have problems on importing struct from other files. Suppose I have the file structure:
- Lattices.jl
- Hamiltonians.jl
- main.jl
Lattices.jl defines a struct called Lattice:
module Lattices
export Lattice
struct Lattice
lx::Int64
ly::Int64
end
end
Hamiltonians.jl defines Hamiltonian on a Lattice:
module Hamiltonians
export Hamiltonian
include("Lattices.jl")
using .Lattices
struct Hamiltonian
lat::Lattice
coeffs::Vector{Float64}
end
end
main.jl tries to create a Hamiltonian:
include("Lattices.jl")
include("Hamiltonians.jl")
using .Lattices
using .Hamiltonians
a = Lattice(16, 16)
ham = Hamiltonian(a, [1.0, 0.0])
display(ham)
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 Lattice from 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 Hamiltonians.
What you want there is to use:
module Hamiltonians
export Hamiltonian
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 Bar1
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
end
end;
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.
The 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:
module MyQuantumPackage
include("lattices/Lattices.jl")
include("Hamiltonian.jl")
end
where Lattices.jl is just:
export Lattice
struct Lattice
lx::Int64
ly::Int64
end
(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.
Main and Foo are names of 2 modules. Modules contain their own names by default and the names of nested modules, so Foo.Foo and 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
export x
x = "outer"
module Foo # don't repeat names in practice
export x
x = :inner
end
module Bar
using ..Foo # Bar -> Foo.Foo
end
end
WARNING: replacing module Foo.
Main.Foo
julia> Foo, Foo.Foo
(Main.Foo, Main.Foo.Foo)
julia> Foo.Bar.x
:inner