我主要是从Javascript背景来到Elixir。在JS中,可以编写更高阶的函数"一次"返回一个只调用传入函数一次的函数,并在后续调用中返回前一个结果 - 该技巧操纵通过闭包捕获的变量:
var once = (func) => {
var wasCalled = false, prevResult;
return (...args) => {
if (wasCalled) return prevResult;
wasCalled = true;
return prevResult = func(...args);
}
}
在我看来,由于其不同的变量重新绑定行为,它无法在Elixir中创建此函数。有没有其他聪明的方法通过模式匹配或递归来做,或者它是不可能的?没有宏,我想象那些可能会启用宏。感谢
答案 0 :(得分:3)
使用当前流程词典:
(@RoleId = 1 AND _t1.CreatedBy = @RoleId) OR ISNULL(@RoleId,0)<> 1
或者,如果函数将跨进程传递,则可以使用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
表:
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
/ Application.put_env
也可用于保存全局状态,但通常用于配置设置。
答案 1 :(得分:2)
在大多数情况下,它不被视为惯用语,但您可以使用Agent
执行此操作:
defmodule A do
def once(fun) do
{:ok, agent} = Agent.start_link(fn -> nil end)
fn args ->
case Agent.get(agent, & &1) do
nil ->
result = apply(fun, args)
:ok = Agent.update(agent, fn _ -> {:ok, result} end)
result
{:ok, result} ->
result
end
end
end
end
现在如果你运行这个:
once = A.once(fn sleep ->
:timer.sleep(sleep)
1 + 1
end)
IO.inspect once.([1000])
IO.inspect once.([1000])
IO.inspect once.([1000])
IO.inspect once.([1000])
您将看到第一行在1秒后打印,但接下来的3行会立即打印,因为结果是从代理商处提取的。
答案 2 :(得分:1)
虽然两个已经给出的答案都是完全有效的,但您的javascript中最准确的翻译如下所示:
defmodule M do
use GenServer
def start_link(_opts \\ []) do
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
end
def init(_args) do
Process.sleep(1_000)
{:ok, 42}
end
def value() do
start_link()
GenServer.call(__MODULE__, :value)
end
def handle_call(:value, _from, state) do
{:reply, state, state}
end
end
(1..5) |> Enum.each(&IO.inspect(M.value(), label: to_string(&1)))
使用@ Dogbert答案中的相同指标:第一个值打印延迟,随后立即打印。
这与使用GenServer
阶段的memoized函数完全类似。 GenServer.start_link/3
返回以下内容之一:
{:ok, #PID<0.80.0>}
{:error, {:already_started, #PID<0.80.0>}}
那就是说,如果已经启动,就不会重启。我不打算检查返回的值,因为我们在任何情况下都设置了:如果它是初始启动,我们调用重函数,如果我们已经启动,则vaklue已经在state
的指针处。