I am confused about the behavior of scoping when using a nested function. I am using Julia 1.4.1.
MWE:
function main()
function doit()
A = ones(Int, 2) # <-- I expected this to introduce a new A within the scope of doit
return A
end
doit()
display(A) # <-- Why is this not an error? I haven't defined A yet
A = [1, 2, 3] # <-- Now A is defined
display(A)
doit()
display(A) # <-- Why is A overwritten? I didn't assign it to the output of doit()
end
Output:
julia> main()
2-element Array{Int64,1}: # Expected an error for undefined A
1
1
3-element Array{Int64,1}: # This is what I expected
1
2
3
2-element Array{Int64,1}: # Expected [1, 2, 3] again
1
1
It looks like there is no new scope introduced for doit. Is that correct? Can someone explain why that is, or if I am misunderstanding something?
My understanding is that when Julia compiles main() it notices that A gets defined in main(). Therefor when the child function set A it’s actually setting A in the parent context. The compiler doesn’t care when A is set just that it was set.
One way to avoid this situation is that when defining the variable prefix it with local that will ensure that the variable is, well, local. So if you did local A = ones(Int, 2) then that A would be local to doit() and you would get the expected error.
Note that nested functions can modify their parent scope’s local variables:
julia> x, y = 1, 2;
julia> function baz()
x = 2 # introduces a new local
function bar()
x = 10 # modifies the parent's x
return x + y # y is global
end
return bar() + x # 12 + 10 (x is modified in call of bar())
end;
julia> baz()
22
julia> x, y # verify that global x and y are unchanged
(1, 2)
The reason to allow modifying local variables of parent scopes in nested functions is to allow constructing closures which have private state
In your example, the function is defined before the variable that it modifies, but the compiler doesn’t care of that — just as in the case of variables defined in the global scope.
Yes, but in the manual x is defined before the nested function is defined. In my example, A was defined after the definition of the nested function. I understand why the example in the manual works as it does because x was created before the nested function was. I don’t understand why the nested function in my example captures a variable I haven’t defined yet.
EDIT: Sorry, I responded thinking I had already scrolled to the end of your post, so I missed
In your example, the function is defined before the variable that it modifies, but the compiler doesn’t care of that — just as in the case of variables defined in the global scope.
This does not address exactly what you are asking, but I think that having a closure that modifies the closed over variables is always walking on a danger zone. I would advice to simply don’t do it, and use patterns like:
julia> function baz()
a = 5
function bar()
x = 2 + a # do not modify a here
end
y = bar()
y
end
baz (generic function with 1 method)
julia> @code_warntype baz()
Variables
#self#::Core.Const(baz)
y::Int64
bar::var"#bar#5"{Int64}
a::Int64
Body::Int64
1 ─ (a = 5)
│ %2 = Main.:(var"#bar#5")::Core.Const(var"#bar#5")
│ %3 = Core.typeof(a::Core.Const(5))::Core.Const(Int64)
│ %4 = Core.apply_type(%2, %3)::Core.Const(var"#bar#5"{Int64})
│ (bar = %new(%4, a::Core.Const(5)))
│ (y = (bar::Core.Const(var"#bar#5"{Int64}(5)))())
└── return y::Core.Const(7)