Anonymous function in function closure

Consider the following simple code:

function myclass(x)
        h() = 1 + x
        g() = 2 + x
        () -> (h,g)
end

I understand

x = 1    
a = myclass(x)
a()            #  (>h, >g)
a()[1]() == 2        # = true as this is h()

but why

a.h() == 2 # true

which is how the above “class” is meant to be used?

You’re seeing an effect of https://github.com/JuliaLang/julia/pull/13412 which redesigned closures to be structs with fields corresponding to the closed-over values.

When you write:

julia> let
         a = 1
         f() = a + 1
         @show f.a
         @show f()
       end
f.a = 1
f() = 2

the compiler transforms it into (roughly):

julia> let
         a = 1
         
         # The anonymous funcion is turned into a `struct`
         struct Anonymous1{T}
           a::T
         end
         # Make intances of that `struct` type callable
         (anon::Anonymous1)() = anon.a + 1
         # Make `f` an instance of that `struct` type
         f = Anonymous1(a)
         @show f.a
         @show f()
       end
f.a = 1
f() = 2

But the other thing I would say is: don’t use this to implement OOP. Multiple dispatch is a really nice way to organize code, and it’s worth learning the way Julia is intended to be used rather than trying to stick all of your functions inside your types.

4 Likes

Do you know why the let block is needed to make your example work? I.e. what’s different at global scope?

At global scope, the closure doesn’t capture the value of a but instead just references the global variable directly:

julia> a = 1
1

julia> f() = a + 1
f (generic function with 1 method)

julia> a = 5
5

julia> f()
6

Looking at the code_lowered shows that the body of f() is accessing Main.a:

julia> @code_lowered f()
CodeInfo(
1 ─ %1 = Main.a + 1
└──      return %1
)

It is indeed surprising that the capture behavior is so different at global vs. local scope.

4 Likes