How to calculate a constant the first time a function is called?

Hello, how to calculate a constant the first time a function is called?
not when compiling
not every call

foo() = (sleep(1); 1)  # foo calculate a constant
bar(x) = foo() + x  # ????

# first call:
@time bar(2)  # -> 1.001082 seconds (5 allocations: 144 bytes)
# second call:
@time bar(2)  # -> 0.000001 seconds

may be, i dont know
but looks like *hit

global FOO = nothing  #

foo() = (sleep(1); global FOO = 1)  # foo calculate a constant

bar(x) = try
    FOO + x
catch
    foo()
    @eval bar(x) = $FOO + x
    return FOO + x
end

@time bar(22)  # -> 1.001544 seconds (247 allocations: 12.344 KiB)
@time bar(22)  # -> 0.000001 seconds

see this thread: In Julia, how to create a function that saves its own internal state?

1 Like

Here’s a pretty simple way to do it:

const FOO = Ref{Union{Int, Nothing}}(nothing)

function foo()
  if FOO[] === nothing
    println("expensive calculation running...")
    sleep(1)
    FOO[] = 1
  end
  FOO[]
end

bar(x) = foo() + x

Demo:

julia> bar(1)
expensive calculation running...
2

julia> bar(2)
3
1 Like

Or, if you do not trust union splitting, you can have one global with a boolean to indicate if the function was already called or not; and the global with the Ref starting with a dummy/undef value.

1 Like

If your function has some input, you might better avoid at all this global state, and wrap the constant precomputed in the input, such that the function is completely self-contained. Something like:

julia> struct MyInput
           x::Vector{Float64}
           constant::Float64
       end

julia> MyInput(x::Vector{Float64}) = MyInput(x, length(x)*sum(x)) # invented a calculation for the constant
MyInput

julia> input = MyInput(rand(3)) # here the constant is computed
MyInput([0.607776859483833, 0.630463715553019, 0.01606684134650216], 3.762922249150063)

julia> function foo!(input)
           (;x, constant) = input
           x .= constant .* x  # mutates the `x` entry of input
       end
foo (generic function with 1 method)

julia> foo!(input)
MyInput([2.2870170670702663, 2.372385942536272, 0.06045827477631713], 3.762922249150063)

julia> foo!(input)
MyInput([8.605867405864627, 8.92710384674058, 0.22749978730103176], 3.762922249150063)

1 Like