Is "include" safe to use inside a function?

Is the following safe to do?

#testinclude.jl
a = 2
julia> function f()
           include("testinclude.jl")
           return a
       end
f (generic function with 1 method)

julia> f()
2
1 Like

It’s safe, but there’s probably a better way to do what you’re trying to do. What is your use case exactly?

1 Like

Awesome. My use case is running experiments programmatically given a function that lives in a file, where the file name and the function name are retrieved from a database. I think I can do without the above feature actually. Thanks though!

1 Like

But note that include includes the code in the global scope, i.e. it’s not just copy-paste, which is what you probably had in mind. Consider including a file tmp.jl which just contains a=1:

julia> function f()
       include("tmp.jl")
       1
       end                                                                                                                                                               
f (generic function with 1 method)                                                                                                                                       

julia> a                                                                                                                                                                 
ERROR: UndefVarError: a not defined                                                                                                                                      

julia> f()                                                                                                                                                               
1                                                                                                                                                                        

julia> a                                                                                                                                                                 
1                                                                                                                                                                        

So, a is a global variable after running the function. So include is like running eval in a local scope.

If you want to copy-paste different bits of code into a function, you could consider using a macro:


julia> macro ins()
       esc(:(a = 1)) # esc is necessary
       end
@ins (macro with 1 method)

julia> function f(x)
       @ins
       x + a
       end
f (generic function with 1 method)

julia> f(4)
5

julia> macro ins()
       esc(:(a = -10))
       end
@ins (macro with 1 method)

julia> f(4) # need to re-define the function to get the update
5

julia> function f(x)
       @ins
       x + a
       end
f (generic function with 1 method)

julia> f(4)
-6

However, in most cases I don’t think this is particularly nice style.

8 Likes

I see, thanks for the detailed response. I opted for a macro to be presumably called by the user in global scope. Since there will be using and include statements needed before running the functions, and I might need parse and eval, I think my application really belongs to global scope. Also type instability is not really an issue in my use case.

This discussion makes me wonder why “include” is not already a macro, rather than a function. After all, even in C is a macro.

Because it can be a function—principle of least privilege.

7 Likes

This discussion should show exactly why it shouldn’t be a macro. By being a function, it should be very clear that it cannot do anything that’s not in global scope. It also makes it clear that the include happens at runtime and you can create a wrapper function on it without ANY semantics significance. Note that these are properties on ALL functions.

If you don’t want any of these properties, you may need a macro. Making it happen at compile time (whether on global or local scope) is trivial to do and I’m pretty sure someone had a macro / package for it. It’s purely for source code organization but since it isn’t particularly easy for the reader and isn’t needed in Base, it is not included in Base.

If you want it to do something to the local scope and at runtime, that’s impossible. The usecase is basically calling user supplied code and it should use callback/function overload instead.

5 Likes

Does Julia allow creating a new environment? So then the experiment can be run in that environment and destroyed afterwards. But if that’s the case then it may be good to just spawn a new process…

You can create new modules. They will almost never be freed though

Does this mean that objects whose only bindings are at global scope in a given module are not garbage-collected even if the module itself is “replaced” by a new module with the same name?

Not always but very likely.

Hi All. I’m a non-expert, but am wanting to code something similar to mohamed82008 here, and wondering what is “the better way to do what you’re trying to do” as you said in your reply to mohamed82008. The reason I want to use include() inside a function is that my data type is HUGE and takes up many lines of code – therefore it’s convenient to stash it away in a separate file via the include() command. And I want the include() inside a function because I want the user to be able to dynamically create different instances of that data type. But am I doing bad code practice here?

I’m wanting to allow the user to create an instance of a data type and then perform operations on it (such as math or plotting). A MWE is below. I’m worried that I’m organizing my code in an inefficient or non-Julia-optimized way, or in a way that hurts speed. What do you think?

module My_Module
    using Plots: plot

    #Define a data type
    struct data_type_A
        a::Array{Float64,1}
        b::Array{Float64,1}
        c::Float64
    end

    #Allow the user to create an instance of that data type
    function create_a_structure(include_filename)
        include(include_filename)
    end

    function multiply_stuff(struct_name)
        struct_name.a[1]=struct_name.a[1]*10
    end

    function plot_data(struct_name)
        plot(struct_name.a , struct_name.b)
    end
end


#inside a file called "input.jl"
one_instance = system_state_data_type(
    [99, 0, 22.0], 
    [19.3, 4.5, 0], 
    67.7)

#Workflow
My_Module.create_a_structure("input.jl")
My_Module.multiply_stuff(My_Module.one_instance)
My_Module.plot_data(one_instance)

You can define a macro that expand or even generate the long code and then use it in your function.

Ok, thank you for the suggestion. Why is a macro better than using include? And why did mauro3 say in this discussion here that using macros is “in most cases I don’t think this is particularly nice style”?

Let me see if I understand your usecase correctly. Do you have different constructions of data_type_A instances in files, like the following but much longer?

file1.jl:

data_type_A(
    [1 2 3 4; 5 6 7 8],
    [0 0 0 0; 0 0 0 0],
    0.5
)

Maybe you should just save the arrays that go into your data type in CSV files or something ismilar?

Because what you want is exactly a macro. What you want isn’t a include function and cannot possibly be done with a function. A macro that doesn’t imply including a file also makes it very clear that the change on the file will never be reflected on the code included.

One reason this is bad style is that it’s the wrong level of abstraction in most cases. You should have a set of well defined input and output for most well defined codeor the code can be very hard to understand. And when you have that very often you can simply use a function instead and there’s no need to splice in the code directly.

Thanks mtsch yes you guessed correct: different constructors of data_type_A stored in files. These constructors are lonnnnng. I like these constructors as Julia files because they involved calculations (a CSV file couldn’t do these calculations), a MWE of one of these constructors stored in a file called “input.jl” or something similar:

m=3
r=exp(m/600)
single_instance=data_type_A(
    [1 r 3 4; 5 6 7 8],
    [0 0 0 0; 0 0 0 0],
    0.5
)

Thank you yuyichao. I will study and research your words. I’m sure you’re correct, but I’m too much of a beginner to fully understand. I feel like my code has well-defined input and output as written in the MWE above.

How would I re-organize my MWE above to use functions instead of splicing in the code?

This code modifies the global value m and r for example. My first intuition is to simply redefine a constructor function via include:

function make_instance()
  m = 3
  r = exp(m/600)
  return(data_type_A([...]))
end

Then you simply include the file to redefine the constructor, and call make_instance()

include("thefile.jl")

inst = make_instance()