Scope and anonymous functions

I need help :smiley:

What is the difference between:

function a() 
        funcs = []
        i = 1
        for j=1:100
           push!(funcs, (args...)->i)
           i += 1
       end
       return funcs
end
b = a()
b[1]()

and

function c() 
        funcs = []
        for i=1:100
           push!(funcs, (args...)->i)
       end
       return funcs
end
d = c()
d[1]()

The first one returns 101 for every call of b[x]() and the second one does the expected thing of d[x]() = x

I put it into a function to avoid global scope issues.

What I also find interesting is that the repl shows something like:

100-element Array{Any,1}:
 #76 (generic function with 1 method)
 #76 (generic function with 1 method)
 #76 (generic function with 1 method)

for both of them which looks like it creates the same function over and over again? (Based on the name #76).

@TheCedarPrince is also interested :smile:

https://docs.julialang.org/en/v1/manual/variables-and-scoping/#Local-Scope

the behavior should be explained here, My guess is your first function should fall into:

When x = <value>

  1. Existing Local

because function is a hard local scope and your assignment is in a soft local scope (for).


One way to make your first function to β€œbehave” is by forcing a local variable:

function a()
               funcs = []
               i = 1
               for j=1:100
                  local k=i
                  push!(funcs, (args...)->k)
                  i += 1
               end
              return funcs
       end

The behavior of a() is due to the way closure works, as all the functions in funcs will return the local variable i as it updates. The second scenario is a bit different because the iterator variable i is new for each iteration. Besides @jling recommendation, it’s also possible to use the let block for the first case.

Thanks for the explanation and the way to avoid this. The other question is why do all those functions have the same name?

Just the way @show works for arrays?

julia> @show d[1]
d[1] = var"#1#2"{Int64}(1)
#1 (generic function with 1 method)

julia> @show d[2]
d[2] = var"#1#2"{Int64}(2)
#1 (generic function with 1 method)

Because that is their name:

# a is generated with the second loop
julia> a[1] === a[2]
false

julia> typeof(a[1]) === typeof(a[2])
true

julia> a[1]
#1 (generic function with 1 method)

julia> a[1]()
1

julia> a[2]()
2

It’s just that they box different values:

julia> dump(a[1])
#1 (function of type var"#1#2"{Int64})
  i: Int64 1

julia> dump(a[2])
#1 (function of type var"#1#2"{Int64})
  i: Int64 2

Remember, anonymous functions are implemented as callable structs, where each variable that is captured refers to a field. The second loop creates multiple different instances of the same struct, which capture different values in their single field. The first one captures a reference to the same value/binding.

Lowered code of your anonymous function
julia> Meta.@lower (args...) -> i
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─      $(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─      global var"#7#8"
β”‚        const var"#7#8"
β”‚   %3 = Core._structtype(Main, Symbol("#7#8"), Core.svec(), Core.svec(), false, 0)
β”‚        var"#7#8" = %3
β”‚        Core._setsuper!(var"#7#8", Core.Function)
β”‚        Core._typebody!(var"#7#8", Core.svec())
└──      return
)))
β”‚   %2 = var"#7#8"
β”‚   %3 = Core.apply_type(Vararg, Core.Any)
β”‚   %4 = Core.svec(%2, %3)
β”‚   %5 = Core.svec()
β”‚   %6 = Core.svec(%4, %5, $(QuoteNode(:(#= REPL[45]:1 =#))))
β”‚        $(Expr(:method, false, :(%6), CodeInfo(quote
    return i
end)))
β”‚        #7 = %new(var"#7#8")
└──      return #7
))))

I learn a lot today :slight_smile: Thanks for the example and explanation,

I talk a little about the scope of variables inside loops here, and I really deepen the topic here.