cppProgrammer
cppProgrammer

Reputation: 349

How can values be cached in an Elixir function?

First, the counter function:

In C++ I can do the following to cache a value across function calls (shamelessly copied from TutorialsPoint.com):

void func( void ) {
   static int i = 5; // local static variable
   i++;
   std::cout << "i is " << i ;
   std::cout << " and count is " << count << std::endl;
}

Second, a database cacheing example:

defmodule ElixirTesting do

  def test_static_function_variable do
    IO.puts(get_value_from_db_cache("A"))
    IO.puts(get_value_from_db_cache("B"))
    IO.puts(get_value_from_db_cache("A"))
    true
  end

  defp get_value_from_db_cache(key) do

    # We want this to be static.
    data_cache = %{}

    if !Map.has_key?(data_cache, key) do
      # Scope of this assignment is limited to local if block so does not re-bind static variable.
      data_cache = Map.put_new(data_cache, key, get_value_directly_from_db(key))
    end

    data_cache[key]
  end

  @db_data %{ "A" => 3, "B" => 4, "C" => 5, "D" => 6 }
  defp get_value_directly_from_db(key) do
    IO.puts("Inside get_value_directly_from_db for key #{key}")
    @db_data[key]
  end

end

Console output:

Inside get_value_directly_from_db for key A
Inside get_value_directly_from_db for key B
Inside get_value_directly_from_db for key A

For both cases how can I achieve the same in Elixir? Or, how should I go about refactoring to accomplish the result using proper functional design?

Upvotes: 0

Views: 604

Answers (1)

Paweł Obrok
Paweł Obrok

Reputation: 23184

Short answer is - you can't. Functional means you program with pure functions, so they always return the same result for the same inputs, caching something inside a function breaks that

Longer answer is - when you want to manage some state, the default in Elixir is to wrap that state in a process. A simple type of process that just keeps a piece of data is called an Agent. For example, you can start a named agent, and reference it in your function like so:

defmodule Test do
  def start_link(initial_value) do
    Agent.start_link(fn -> initial_value end, name: MyAgent)
  end

  def put(value), do: Agent.update(MyAgent, fn _ -> value end)

  def get, do: Agent.get(MyAgent, fn value -> value end)
end

Test.start_link(:value1)

Test.get() |> IO.inspect() # => :value1
Test.put(:value2)
Test.get() |> IO.inspect() # => :value2

Upvotes: 2

Related Questions