"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.

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.

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.

As an add-on question here, what is the struct that contain the anonymous functions called? How is it accessed? I want to better understand the logic behind this, but cant find good answers on how this struct is accessed

Methinx it’s undocumented, but a closure, i.e. an anonymous function which accesses external variables, is a quite ordinary struct which can be accessed via the dot-notation:

julia> f = let x = 4; i -> i+x; end
#5 (generic function with 1 method)

julia> f.x
4

julia> dump(f)
#5 (function of type var"#5#6"{Int64})
  x: Int64 4

You can do the same with the arr array:

julia> dump(arr[4])
#9 (function of type var"#9#10"{Int64})
  i: Int64 3

julia> arr[4].i
3

In this case the struct is a parametric struct var"#9#10" with the parameter Int64. The var"..." syntax is used when the name is syntactically weird, i.e. you can’t use #9#10 as an ordinary name in a julia program. You can look at it, just as you can with a

struct MyStruct{i} <: Function
    i::i
end

julia> dump(MyStruct)
UnionAll
  var: TypeVar
    name: Symbol i
    lb: Union{}
    ub: Any
  body: MyStruct{i} <: Function
    i::i

julia> dump(var"#9#10")
UnionAll
  var: TypeVar
    name: Symbol i
    lb: Union{}
    ub: Any
  body: var"#9#10"{i} <: Function
    i::i

You can then go on to make our MyStruct callable:

julia> (s::MyStruct)(a) = s.i  + a

julia> myfun = MyStruct(23)
(::MyStruct{Int64}) (generic function with 1 method)

julia> myfun(2)
25

There’s not a structure that contains all anonymous functions, nor are there many structs that contain each anonymous function. Each anonymous function is itself a stand-alone struct that’s a subtype of Function, just like @sgaure demonstrates. They’re defined as needed when Julia “lowers” your code — it’s kinda like a syntax expansion pass. If you’re intrepid, you can read what’s happening when you define anonymous functions using Meta.@lower — here’s what happens when you have a closure:

Meta.@lower f(x) = ()->x+1
julia> Meta.@lower f(x) = ()->x+1
:($(Expr(:thunk, CodeInfo(
1 ─       $(Expr(:thunk, CodeInfo(
1 ─     return $(Expr(:method, :(Main.f)))
)))
│         $(Expr(:method, :(Main.f)))
│         $(Expr(:thunk, CodeInfo(
1 ─      global var"#f##0#f##1"
│   %2 =   dynamic Core.TypeVar(:x, Core.Any)
│   %3 =   builtin Core.svec(%2)
│   %4 =   builtin Core.svec(:x)
│   %5 =   builtin Core.svec()
│   %6 =   builtin Core._structtype(Main, Symbol("#f##0#f##1"), %3, %4, %5, false, 1)
│          builtin Core._setsuper!(%6, Core.Function)
│        $(Expr(:const, :(Main.:(var"#f##0#f##1")), :(%6)))
│   %9 =   builtin Core.svec(%2)
│          builtin Core._typebody!(%6, %9)
└──      return nothing
)))
│   %4  = var"#f##0#f##1"
│   %5  =   builtin Core.svec(%4)
│   %6  =   builtin Core.svec()
│   %7  =   builtin Core.svec(%5, %6, $(QuoteNode(:(#= REPL[1]:1 =#))))
│         $(Expr(:method, false, :(%7), CodeInfo(
    @ REPL[1]:1 within `unknown scope`
1 ─ %1 = Main.:+
│   %2 =   builtin Core.getfield(#self#, :x)
│   %3 =   dynamic (%1)(%2, 1)
└──      return %3
)))
│   %9  = Main.f
│   %10 =   dynamic Core.Typeof(%9)
│   %11 =   builtin Core.svec(%10, Core.Any)
│   %12 =   builtin Core.svec()
│   %13 =   builtin Core.svec(%11, %12, $(QuoteNode(:(#= REPL[1]:1 =#))))
│         $(Expr(:method, :(Main.f), :(%13), CodeInfo(
    @ REPL[1]:1 within `unknown scope`
1 ─ %1 = var"#f##0#f##1"
│   %2 =   dynamic Core._typeof_captured_variable(x)
│   %3 =   builtin Core.apply_type(%1, %2)
│        #1 = %new(%3, x)
│   %5 = #1
└──      return %5
)))
│   %15 = Main.f
└──       return %15
))))

Effectively, it’s doing this:

# f(x) = ()->x+1
function f end
struct var"#f##0#f##1"{x} <: Function
    x::x
end
(self::var"#f##0#f##1")() = self.x+1
f(x) = var"#f##0#f##1"{typeof(x)}(x)