I don't understand how include works in combination with modules

Hello, I am new to julia and I struggling with understanding how modules work.
As far as I know include just copy-pastes code into the current file.
I have a PointModule that does has a struct and a distance function. It’s here just for demonstration purposes.

module PointModule 

export Point,distance 

struct Point
    x
    y 
end
distance(x::Point) = 10

end 

and then in mail.jl I import the module like so which works and prints “10”.

module MainCode

include("../PointModule/src/PointModule.jl")
using .PointModule

distance(Point(1,2))
end

In the same folder as main.jl I have defined the file test.jl that includes main.jl and wants to use PointModule.

include("main.jl")

using .MainCode
using .PointModule
println("Test")
distance(Point(1,2))

I would expect this code to work because the include(“main.jl”) would also trigger the include of the PointModule.

Unfortunately, it gives the error “PointModule not defined”

1 Like

Hi @nickk2002 and welcome to the forum!

That’s exactly how include works, as far as I know. There is just one more level of nested modules in your example: PointModule is available within the module MainCode, but you don’t export the name to the “global” module at the top.

So one of these should work

  1. Importing the name from “within” the MainCode module
include("main.jl")

using .MainCode
using .MainCode.PointModule
println("Test")
distance(Point(1,2))
  1. Adding export PointModule to the body of the MainCode module definition. So when you do using .MainCode, the export PointModule also makes this name available to use and the using .PointModule in the Main module works.

Hi @Sevi. I modified the code as you said and it works!
So this means the PointModule is now a nested module? It seems a bit counter intuitive because PointModule should be standalone and not depend on MainCode

I would want to ask if there is a way for the MainCode to export all functionality that PointModule has without manually exporting all functions/structs.

1 Like

I just call it “nested” module, but in the end, the issue is just how to get the names of objects and where you can “see” which names (modules are also just objects and they have a name).

So if you have

module A

module B
    x = 10
end

end

then within A, but outside of B you cannot use* the name x (it is only defined in B and not known in A), but the name of the module B itself is known in A, if that makes sense. So if you want to use x in A you have to either access it directly as B.x or export x from B and then do using B inside A.
One level higher, the same is true: If you want to use B outside of A, you first have to make the name of B available outside of A using the same techniques.

Regarding names, there is no real distinction between a “normal” object (like the integer x) and a module (like B). Basically “everything is an object.”

PS: I’m glad it worked! :smiley:

This package might be helpful


*Of course you can use the variable name x, but only if you define it as something. It won’t be bound to the same object as B.x. You cannot reach this particular object with just the name x is what I meant.

1 Like

If you feel like the functionality of PointModule should not depend on MainModule then you should probably include the code directly where you want to use the point code.

It’s also perfectly fine to not use modules at all. It is mostly useful when writing packages: to use a package, there should be one top-level module which can be imported just with using Package, but it might be useful to show which are the “logical” parts within Package by making “sub-modules” inside. But this seems to be largely a matter of taste (whether or not to keep all the code of a package in one module or make sub-modules).

1 Like

Note that you will have fewer problems if you just do:

# points.jl file
struct Point{T}
    x::T
    y::T 
end
distance(x::Point) = 10

and

# MainModule.jl
module MainModule
    export Point, distance
    include("points.jl")
    distance(Point(1,2))
end

that is, if instead of modules, you just include the files. Most Julia packages are structured simply like that (but yes, you can find a lot of discussions about this organization here, I won’t restart that).

3 Likes

I have an explanation of what I think is a good way to structure Julia projects in a recent thread How to structure project in julia - #44 by Henrique_Becker

1 Like

You may also like FromFile.jl, which is an alternate (IMO imprived) import system for Julia.

Thank you for all the fast comments. I am really impressed by how warm and kind the julia community is with beginners.

Context


My real use case of the problem is similar to my initial question. I am working on developing a library for program synthesis (creating programs that have a certain grammar an satisfy input/output examples. for instance given 10 examples as 1,2 | 2,3 | 3,4 | ... 10,11 the algorithm should realize that the function that relates input to output is f(x) = x + 1).

The code base is already structured using modules like Search, Grammar, Evaluation, Constraints and in order to run everything there is a module Main that includes all the modules and adds the necessary usings.

module Main

include("path/to/Grammar.jl")
include("path/to/Constraints.jl")
include("path/to/Search.jl")


using .Grammar
using .Constraints
using .Search


export 
    Grammar,
    Constraints,
    Search

end # module

To keep the code clean whenever I want to test if an algorithm works I have include all the submodules in a Temporary.jl even though I call include("Main.jl")
Thus Temporary.jl has something like

include("src/Main.jl")
using .Main
using .Grammar
using .Constraints
using .Search

// code that creates a problem with examples and tries out an algorithm on it.

It is a bit unfortunate that even though I have the include to Main.jl I still need to add all the usings.
I think that for my use case Reexport.jl would be best suited.

@lmiq I can’t really stop using modules because the code base is already structured like that. The reason is that each modules (e.g Search, Grammar, Constraints) will need to be published separately when the library is released.

My new question


Whenever I make a small change in Search for instance I need to kill the Julia REPL from VsCode and start another one. This is quite time consuming because the REPL takes some time to start.

I saw that Revise.jl can solve this problem but I am quite new to Julia and the from the documentation I see that I only need using Revise at the top of Temporary.jl.

Unfortunately it does not work as expected.
When I go to VsCode and change a function from Search to print some debug information and I run Temporary.jl in the Repl by clicking the run button I don’t see any output from the print function.

Only if I kill the Julia session and start it again I will see the print statements.

Thank you in advance for taking the time to read through this long post and answer my question :slight_smile:

I have only used Revise.jl in the REPL (without an IDE), in this case I do run import Revise; Revise.includet("main.jl") directly in the REPL, and then run start making calls testing the code provided by main.jl. If I go and change something in main.jl or something it includes and then go back to the REPL and re-run some function then I see the difference.

1 Like

It is includet with a t.

This is how I do it: Development workflow · JuliaNotes.jl

If your modules can be used independently, consider making each of them a package, then you can dev the packages with Revise and all changed will be tracked. Making them packages is nothing very complicated (Create new package · JuliaNotes.jl).

With that the dependency tree of your package will work better, without the need of includes+usings.

1 Like