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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With