Hey!
I would like to create a function with an internal state, but I do not want to have to initialize this internal state outside of the function (I want to rely only on calls to this function and I do not want the function to return anything). I think the following code does what I mean:
function foo()
if @isdefined state
global state
state += 1
else
global state
state = 1 # 1st call: initialize internal state
end
end
which gives the following expected output:
julia> foo()
1
julia> foo()
2
julia> foo()
3
However, I see drawbacks with this code, e.g., maybe working with global mutable variable is not the best way as it is computationally expensive. On the other hand, I cannot think of a way to achieve what I described without using global variables. Note that the state of the function would only be accessed by the function itself. Any thought / advice?
julia> let state = Ref{Union{Int, Nothing}}(nothing)
global f
function f()
if state[] !== nothing
state[] += 1
else
state[] = 1
end
state[]
end
end
f (generic function with 2 methods)
julia> f()
1
julia> f()
2
julia> f()
3
Note that we need global f just to ensure that the function f is defined in global scope. This doesnāt cause any performance issues because functions are (implicitly) const, and constant globals are ok.
One way to do this is by spawning a coroutine (a task) and then ask that task to give you data:
function coroutine(c::Channel)
state = rand(100)
for s in state
push!(c, s)
end
return
end
c = Channel(1)
t = @async coroutine(c)
take!(c)
take!(c)
# etc
Exactly how to structure it depends a bit on your application.
struct F
state
end
(o::F)(x) = o.state + x
f = F(1)
f(2)
3
more specifically:
julia> Base.@kwdef mutable struct F
state = nothing
end
F
julia> function (o::F)()
if o.state == nothing
o.state = 1
else
o.state += 1
end
end
julia> f = F()
F(nothing)
julia> f()
1
julia> f()
2
julia> f()
3
julia> f.state # now this is part of the language, because f is an instance of F
3
Thank you all!
All those solutions work for me. After benchmarking with @btime, I tend to favor the let block as it took 5 times less execution time than the other methods.
If I benchmarked everything correctly, the functor with the pure-Int field is by far more efficient. Followed by the functor with the Union{Nothing,Int} field:
julia> include("state.jl")
let: 3.595 Ī¼s (0 allocations: 0 bytes)
# functor with `Any` field
f1: 29.342 Ī¼s (1000 allocations: 15.62 KiB)
f1_const: 28.184 Ī¼s (1000 allocations: 15.62 KiB)
# functor with Union field:
f2: 1.260 Ī¼s (0 allocations: 0 bytes)
f2_const: 1.259 Ī¼s (0 allocations: 0 bytes)
# functor with pure Int field
f3: 257.421 ns (0 allocations: 0 bytes)
f3_const: 257.424 ns (0 allocations: 0 bytes)
Code
let state = Ref{Union{Int, Nothing}}(nothing)
global f
function f()
if state[] !== nothing
state[] += 1
else
state[] = 1
end
state[]
end
end
Base.@kwdef mutable struct F1
state = nothing
end
function (o::F1)()
if o.state == nothing
o.state = 1
else
o.state += 1
end
end
f1 = F1()
const f1_const = F1()
Base.@kwdef mutable struct F2
state::Union{Nothing,Int} = nothing
end
function (o::F2)()
if o.state == nothing
o.state = 1
else
o.state += 1
end
end
f2 = F2()
const f2_const = F2()
Base.@kwdef mutable struct F3
state::Int = 0
end
function (o::F3)()
if o.state == 0
o.state = 1
else
o.state += 1
end
end
f3 = F3()
const f3_const = F3()
function update_state!(f,n)
for i in 1:n
f()
end
end
using BenchmarkTools
n = 1000
print("let:"); @btime update_state!($f,$n)
print("f1:"); @btime update_state!($f1,$n)
print("f1_const:"); @btime update_state!($f1_const,$n)
print("f2:"); @btime update_state!($f2,$n)
print("f2_const:"); @btime update_state!($f2_const,$n)
print("f3:"); @btime update_state!($f3,$n)
print("f3_const:"); @btime update_state!($f3_const,$n)