"Names" of anonymous functions in loops

I’ve recently been messing around with anonymous functions and loops, trying to generate arrays of functions that are “nested”, i.e. a function generated in a given loop iteration uses functions from previous loop iterations.

I think an example will best explain it.

arr = Array{Function,1}([x -> x + 1])
for i = 1:10
    push!(arr, x -> arr[i](x) + 1)
end

I start off the array arr with the anonymous function x -> x + 1, and then in each iteration of the loop, push another anonymous function on to the array that takes the previous iteration’s function and adds one to its result: push!(arr, x -> arr[i](x) + 1).

This works as I’d expect, given that the following loop prints the sequence 2 to 12.

for func in arr
    println(func(1))
end

But when I look at arr in the REPL, I get:

julia> arr
11-element Array{Function,1}:
 #104 (generic function with 1 method)
 #106 (generic function with 1 method)
 ⋮
 #106 (generic function with 1 method)
 #106 (generic function with 1 method)

It’s always the same pattern – the first element is a certain #-number, and all of the other elements are the same other #-number. Why is this? Like I said, I get the functionality I want, but shouldn’t the #-number be different for every function in the resulting array, as they are “different” functions?

Anonymous functions are implemented by creating a callable struct with a gensymmed name and then creating instances of that struct.

The first one is an instance of the first struct, while the rest are instances of the second.

3 Likes

Just to complement, the struct they are lowered to has a single Int field (or a Ref{Int}, not sure) that will save the i so even for different values of i they end up being the “same” function.

1 Like

for example

julia> fs = [x -> x + i for i = 10:12]
3-element Vector{var"#4#6"{Int64}}:
 #4 (generic function with 1 method)
 #4 (generic function with 1 method)
 #4 (generic function with 1 method)

julia> typeof(fs[1])
var"#4#6"{Int64}

julia> typeof(fs[2])
var"#4#6"{Int64}

julia> fs[1].i # digging into internals
10

julia> fs[2].i
11

julia> fs[1] == fs[2]
false

As you can see, they are not the same. They just have the same type and print the same.

4 Likes