Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Julia closure with a let block - is this kosher?

Tags:

closures

julia

I have cobbled together a closure in Julia using a let block.

counter = let
    local counter = 0          # local variable
    f() = counter += 1         # returned function
end

counter(); counter(); counter()
print(counter()) # 4

Is this kosher in Julia?

like image 561
Mark Graph Avatar asked Oct 24 '20 10:10

Mark Graph


Video Answer


1 Answers

It is valid. Here are two alternative ways to get a similar thing:

julia> function get_counter()
           counter = 0
           return () -> (counter += 1)
       end
get_counter (generic function with 1 method)

julia> c1 = get_counter()
#1 (generic function with 1 method)

julia> c1()
1

julia> c1()
2

julia> c1()
3

julia> mutable struct Counter
           counter::Int
           Counter() = new(0)
       end

julia> (c::Counter)() = (c.counter += 1)

julia> c2 = Counter()
Counter(0)

julia> c2()
1

julia> c2()
2

julia> c2()
3

The difference is that c2 is type stable, while your counter and c1 are not type stable:

julia> @code_warntype counter()
Variables
  #self#::var"#f#1"
  counter::Union{}

Body::Any
1 ─ %1 = Core.getfield(#self#, :counter)::Core.Box
│   %2 = Core.isdefined(%1, :contents)::Bool
└──      goto #3 if not %2
2 ─      goto #4
3 ─      Core.NewvarNode(:(counter))
└──      counter
4 ┄ %7 = Core.getfield(%1, :contents)::Any
│   %8 = (%7 + 1)::Any
│   %9 = Core.getfield(#self#, :counter)::Core.Box
│        Core.setfield!(%9, :contents, %8)
└──      return %8

julia> @code_warntype c1()
Variables
  #self#::var"#2#3"
  counter::Union{}

Body::Any
1 ─ %1 = Core.getfield(#self#, :counter)::Core.Box
│   %2 = Core.isdefined(%1, :contents)::Bool
└──      goto #3 if not %2
2 ─      goto #4
3 ─      Core.NewvarNode(:(counter))
└──      counter
4 ┄ %7 = Core.getfield(%1, :contents)::Any
│   %8 = (%7 + 1)::Any
│   %9 = Core.getfield(#self#, :counter)::Core.Box
│        Core.setfield!(%9, :contents, %8)
└──      return %8

julia> @code_warntype c2()
Variables
  c::Counter

Body::Int64
1 ─ %1 = Base.getproperty(c, :counter)::Int64
│   %2 = (%1 + 1)::Int64
│        Base.setproperty!(c, :counter, %2)
└──      return %2

Adding counter::Int = 0 in your counter or c1 definitions will make it type stable, but you will have Julia do boxing/unboxing anyway. So in conclusion using a functor (c2 version) is what I would normally opt for. Alternatively you could have made your (or c1) version type stable by using Ref wrapper like this:

julia> counter = let
           local counter = Ref(0)     # local variable
           f() = counter[] += 1         # returned function
       end
(::var"#f#1"{Base.RefValue{Int64}}) (generic function with 1 method)

julia> counter()
1

julia> counter()
2

julia> counter()
3

julia> @code_warntype counter()
Variables
  #self#::var"#f#1"{Base.RefValue{Int64}}

Body::Int64
1 ─ %1 = Core.getfield(#self#, :counter)::Base.RefValue{Int64}
│   %2 = Base.getindex(%1)::Int64
│   %3 = (%2 + 1)::Int64
│   %4 = Core.getfield(#self#, :counter)::Base.RefValue{Int64}
│        Base.setindex!(%4, %3)
└──      return %3
like image 62
Bogumił Kamiński Avatar answered Dec 20 '22 07:12

Bogumił Kamiński