Included code behaves different than code in place

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