Including modules inside blocks vs non-top-level modules

Suppose I have a file test.jl

mode = "include"
if mode == "include"
else mode  == "direct"
    #    println("HELLO DIRECT")

and mod.jl

module test
    function print_hello()
        println("HELLO MOD")

This works fine, but if I were to un-comment the “direct” mode, I’d get LoadError: syntax: "module" expression not at top level.

I’m a little bit confused as to what exactly is going on. On some level, I can understand not allowing to define a module inside an if statement or a loop. On the other hand, the include construction is quite useful. Mainly, though, why is include different? I was under the impression that include is basically, “insert code here”. I guess that’s wrong? Should I read the docstring of include

Evaluate the contents of the input source file in the global scope of module m

to mean that an include statement anywhere inside a module basically says, “insert code at the top of the current module”, instead of “insert code where the include statement is”?

I’m still struggling a bit with scoping in Julia, but I suppose scope might be the difference here: a local definition of module would be inside the scope of the if-block (not allowed), whereas the include is at the scope of the Main module (allowed). Is that the correct interpretation?

Yes, exactly: include evaluates the file contents in the global scope of the current module, i.e. as if it was written in the top-level of the current module. If you know about C / C++, maybe it helps to emphasize that – although their names are very close – Julia’s include() function is not akin to C’s #include pre-processing macro in this respect.

If you want the module declaration to work from an if / else clause, you can wrap it inside @eval, which will in this case have more or less the same effect as include: evaluate the statement in the global scope of the current module:

julia> if true
           module Foo
              println("HELLO DIRECT")
ERROR: syntax: "module" expression not at top level
 [1] top-level scope
   @ REPL[2]:1
julia> if true
           @eval module Foo
              println("HELLO DIRECT")

Thanks! My mental model was indeed that of C/C++ #include.

Also, thanks for clarifying @eval, since I had the same misconception there. I thought it would evaluate in the current scope. But now that you point it out, the documentation is actually quite clear that it’s the global scope, not the local scope!

And this might be nit-picking, but maybe it helps clarifying something here: it’s not entirely a question of scope, because if blocks don’t introduce local scopes. It’s really about module appearing at the top-level. (I suspect this has to do with compiler internals, world age issues and such topics, but then we’re speaking of things I don’t really know about)

Background on eval and world age:

1 Like