在Elixir中编写函数“ once”

lt1*_*lt1 5 functional-programming memoization immutability elixir

我主要是从Java语言背景来学习Elixir的。在JS中,可以编写一个更高阶的函数“一次”,该函数返回一个仅调用传入函数一次的函数,并在后续调用中返回前一个结果-技巧是处理通过闭包捕获的变量:

var once = (func) => {
    var wasCalled = false, prevResult;
    return (...args) => {
        if (wasCalled) return prevResult;
        wasCalled = true;
        return prevResult = func(...args);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,由于Elixir不同的变量重新绑定行为,因此无法在Elixir中创建此函数。是否还有其他聪明的方法可以通过模式匹配或递归来实现,或者只是不可能?没有宏,我想那些可以启用它。谢谢

Mik*_*hot 4

使用当前进程字典:

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
Run Code Online (Sandbox Code Playgroud)

或者,如果函数将跨进程传递,则ets可以使用表:

# ... 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
Run Code Online (Sandbox Code Playgroud)

Application.put_env/Application.get_env也可用于保存全局状态,但通常用于配置设置。

  • @MikeBuhot 完全正确。我之前没有看到这个评论,因为我正忙着写我的答案:) (2认同)