In Julia, how to create a function that saves its own internal state?

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?

5 Likes

You can use a let block to hide the state:

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.

10 Likes

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.

4 Likes

Use a closure:

julia> f(state=0) = ()->state+=1
f (generic function with 2 methods)

julia> foo = f()
#7 (generic function with 1 method)

julia> foo()
1

julia> foo()
2

julia> foo()
3

julia> foo.state
Core.Box(3)

julia> foo.state.contents
3
8 Likes

Just to complement,

is not considered part of the language API and may change in the future, breaking code that makes use of this feature.

3 Likes

Or a functor?

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

17 Likes

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.

2 Likes

I would give @lmiqā€™s functor approach another try with

const f = F()
3 Likes

And/Or declaring state with:

state :: Union{Int, Nothing}

inside the struct?

(If nothing can be a zero probably Int only is even better)

1 Like

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)

7 Likes

Think this is the simpliest ā€¦

GlobalBool = [false, false , false , false, false , false , false]

then the function :-

function IdentifyEdgeOfPress(PB :: Int , GlobalBoolPtr :: Int ) :: Int
bTemp = gpio_read(PB)

if (( bTemp == true ) && ( GlobalBool[GlobalBoolPtr] == false )) ; println("Pressed Edge ") ; end
if (( bTemp == false ) && ( GlobalBool[GlobalBoolPtr] == true )) ; println(ā€œReleased Edgeā€) ; end

global GlobalBool[GlobalBoolPtr] = bTemp
end

then just call the function with a different index ( the indices must be not be repeated otherwise thereā€™ll be cross contamination )

	IdentifyEdgeOfPress(TestPB,1)
	IdentifyEdgeOfPress(TunePB,2)

Bingo - you have a subroutine retaining itā€™s own state.
( Thought there might be a more elegant ā€˜Juliaā€™ way of doing this )