Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing the function "once" in Elixir

I'm coming to Elixir from primarily a Javascript background. in JS, it's possible to write a higher order function "once" which returns a function that will invoke the passed in function only once, and returns the previous result on subsequent calls- the trick is manipulating variables that were captured via closure:

var once = (func) => {
    var wasCalled = false, prevResult;
    return (...args) => {
        if (wasCalled) return prevResult;
        wasCalled = true;
        return prevResult = func(...args);
    }
}

It seems to me that it's not possible to create this function in Elixir, due to its different variable rebinding behavior. Is there some other clever way to do it via pattern matching or recursion, or is it just not possible? Without macros that is, I'd imagine those might enable it. Thanks

like image 748
lt1 Avatar asked Nov 23 '17 09:11

lt1


People also ask

How do you define a function in Elixir?

Elixir provides us the ability to define private functions that can be accessed from within the module in which they are defined. To define a private function, use defp instead of def. For example, defmodule Greeter do def hello(name), do: phrase <> name defp phrase, do: "Hello " end Greeter.

How does anonymous function define Elixir?

To define an anonymous function in Elixir we need the fn and end keywords. Within these we can define any number of parameters and function bodies separated by -> .

How do I return an Elixir value?

Elixir has no 'break out' keyword that would be equivalent to the 'return' keyword in other languages. Typically what you would do is re-structure your code so the last statement executed is the return value.


1 Answers

Using the current process dictionary:

defmodule A do
  def once(f) do
    key = make_ref()
    fn ->
      case Process.get(key) do
        {^key, val} -> val
        nil -> 
          val = f.()
          Process.put(key, {key, val})
          val
      end
    end
  end
end

Or if the function will be passed across processes, an ets table can be used:

# ... during application initialization
:ets.new(:cache, [:set, :public, :named_table])


defmodule A do
  def once(f) do
    key = make_ref()
    fn ->
      case :ets.lookup(:cache, key) do
        [{^key, val}] -> val
        [] -> 
          val = f.()
          :ets.insert(:cache, {key, val})
          val
      end
    end
  end
end

Application.put_env / Application.get_env can also be used to hold global state, though usually is used for configuration settings.

like image 195
Mike Buhot Avatar answered Oct 03 '22 12:10

Mike Buhot