Included code behaves different than code in place

Included code behaves different than code in place

In file test.jl:

module test

function sumto(n)
    s = 0 
    for i = 1:n
        s = s + i 
    end
    s 
end

end # module test

This works:

julia>include("test.jl")
julia> test.sumto(5)
15

But if put the content of the function in a different file and include it I get an error.

In the file test2.jl

module test2

function sumto(n)
    include("test3.jl") 
end

end # module test

in file test3.jl:

    s = 0 
    for i = 1:n
        s = s + i 
    end
    s 
julia>include("test2.jl")
Main.test2
julia> test2.sumto(5)
┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable.
â”” @ C:\Gerhard\Model\Julia\MarketFlow\src\test3.jl:3
ERROR: UndefVarError: n not defined
Stacktrace:
 [1] top-level scope at C:\Gerhard\Model\Julia\MarketFlow\src\test3.jl:2
 [2] include(::Function, ::Module, ::String) at .\Base.jl:380
 [3] include at .\Base.jl:368 [inlined]
 [4] include at C:\Gerhard\Model\Julia\MarketFlow\src\test2.jl:1 [inlined]
 [5] sumto(::Int64) at C:\Gerhard\Model\Julia\MarketFlow\src\test2.jl:6
 [6] top-level scope at REPL[2]:1

I do not understand the warning since s is only a local variable in a function.
And I do not understand the error. Why ist “n not defined”?
If include just works as if the code is in the place where the include command is placed, should just work as in the original test file.

I am using:

 julia> versioninfo()                                                                    
 Julia Version 1.5.3                                                                     
 Commit 788b2c77c1 (2020-11-09 13:37 UTC)                                                
 Platform Info:                                                                          
   OS: Windows (x86_64-w64-mingw32)                                                      
   CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz                                         
   WORD_SIZE: 64                                                                         
   LIBM: libopenlibm                                                                     
   LLVM: libLLVM-9.0.1 (ORCJIT, skylake)                                                 
 Environment:                                                                            
   JULIA_PKG_DEVDIR = C:\Gerhard\Model\Julia\MarketFlow\src                              
   JULIA_EDITOR = "C:\Users\totschnigg\AppData\Local\Programs\Microsoft VS Code\Code.exe"
   JULIA_NUM_THREADS =          
2 Likes

You will find long and exhaustive threads discussing how Julia ended up with that behavior, and why it is a good compromise between the pros and cons of many other alternatives. Just search for “scoping rules” in the forum.

Yet, being relatively new to Julia and having to teach in Julia, my perhaps didactic explanation on the choices made is:

  1. Ideally one would like that a loop like
s = 0.
for i in 1:3
  s = s + i
end

worked always and modified s as intended. Yet, in Julia, for performance reasons, the for loop introduces a new scope, where the variables have to be of constant types. Therefore, that behavior cannot be simply accepted without notice.

  1. There is no problem in writing such a loop inside a function, because there the types of the variables are constant and thus the loop is performant. No problems there.

  2. That loop written in the global scope will be problematic (slow) because s might not have a constant type. Thus, one should warn the user that that is not a good programing style. Yet (since Julia 1.5), it was decided that at the REPL that will work, because otherwise one would not be able to copy and paste a loop from a function to the REPL to test it, and that is annoying. This is acceptable because nobody writes critical performant code directly to the REPL.

  3. That leaves the possibility of writing such loops inside files, outside functions. A programmer can be tempted to write critical code inside a file in such a way and, while that is not impossible, it must be discouraged. Thus, the choice was to raise a Warning associated with the possible scoping problems of that loop if it is written as if it was in the global scope of that file. The warning is clear and is saying: don’t do that, unless you are really really aware of its consequences.

  4. Finally, your function includes the file, and in the global scope (it is not as if the function contained that code in the first place):

julia> function f(n)
         include("./badloop.jl")
       end
f (generic function with 1 method)

julia> @code_lowered f(5)
CodeInfo(
1 ─ %1 = Main.include("./badloop.jl")
└──      return %1
)

That means that you can change the content of the file and execute your function again, and it will return a different result. Of course that is not what you want. And since it was included in the global scope, n is not defined there.

1 Like

Thank you for this informative link! This explains it.

1 Like