Keeping modules "clean" when including files

Hi,
I’m writing a module to test the code of a project I’m working on. I though of using multiple files containing the input data and then combine them to obtain all the test cases I need.
To better explain what I want to do consider the following 3 files:

  1. “a.jl”
a=61
  1. “b.jl”
b=2
  1. “Test.jl”
module Test
export @run
c = 10.0

macro run(f1, f2)
    code = quote
        include(@eval $f1)
        include(@eval $f2)
        d = 1.1
        a+b+c+d
    end
    return :($code)
end
end

The files a.jl and b.jl define the data (with variable names which are fixed), while Test.jl contains the module for testing. When I use this code I get the following output:

julia> include("Test.jl")
Main.Test

julia> Test.@run("a.jl", "b.jl")
74.1

julia> Test.a
61

julia> Test.b
2

julia> Test.c
10.0

julia> Test.d
ERROR: UndefVarError: d not defined

As expected the variables a and b are added to Test by the includes, while d is available only in the macro.
What I would like to achieve is that after running the macro @run the module stays “clean” from the variables defined in the included files. For example, in the example above I would like a and b to be not define in the module Test after calling @run.
Is this possible?
Thanks
Carlo

include() always works at global scope within the module. So if the file you are including contains something like a = 1, then no, there is no way to include() that file without it creating a global variable named a.

This is by design. include() is not meant to be used to paste in a bunch of code somewhere inside the body of a function, which is essentially what you’re asking it to do.

Relatedly, the macro isn’t necessary–it just obscures what is actually going on. We can achieve the same behavior with a function:

julia> module Test
       function run(file)
         @eval Test begin
           include($file)
           d = 1.1
           a + d
         end
       end
       end
WARNING: replacing module Test.
Main.Test

julia> Test.run("a.jl")
2.1

julia> Test.a
1

where "a.jl" just consists of the line a = 1. This has the same behavior and the same problem as your original code.

In my experience, the fundamental problem here is trying to store data using code. If, instead, you store your data as data, the problem goes away. For example, we could create a file "a.json" which looks like:

{
  "a": 1
}

and then write our analysis code like this:

julia> using JSON

julia> function run(file)
         data = JSON.parsefile(file)
         d = 2
         data["a"] + d
       end
run (generic function with 1 method)

julia> run("a.json")
3

suddenly all of the complexity goes away. No macros, or evals or includes are necessary, and no extraneous variables are created. Everything works exactly as intended.

Of course, there’s the minor inconvenience that you have to access data["a"]. You can trivially bind that to a local variable a = data["a"], and you could even create a macro to make that eaiser. In my experience that’s almost never a significant problem. If your data has a huge list of variables and no structure at all, then that’s an indication that it’s worth spending some time to make a structure to your data.

4 Likes

Thank you for the detailed answer. In my original post I didn’t mention it, but in the files I want to include I have also some Julia code. I’ll think if i can organize the test differently. In the meanwhile I just realized that if I include Test.jl after running using @run the module is redefined and therefore it does not contain the unwanted variables: not an elegant solution but if I don’t find anything else it works.