提问人:eje211 提问时间:11/15/2023 更新时间:11/16/2023 访问量:81
用 Elixir 制作带有封口的计数器
Making a counter with a closure in Elixir
问:
我正在学习 Elixir,我刚刚谈到了关于闭合的部分。当一门语言有闭包时,我通常做的第一件事就是尝试制作闭包算法。在 JavaScript 中,它看起来像这样:
let counter = function() {
let count = 0;
return function() {
count += 1;
return count;
};
}();
然后,每次调用时,它将按顺序返回一个新号码。counter
counter(); // returns 1
counter(); // returns 2
counter(); // returns 3
counter(); // etc.
有没有可能在长生不老药中做到这一点?主要问题似乎是在Elixir中是不可变的。我可以把它做成一个单元素列表,但这听起来像是一个坏主意™。处理这种纯粹假设情况的灵丹妙药方法是什么?count
答:
主要问题似乎是在Elixir中是不可变的。
count
iex(1)> count = 1
1
iex(2)> IO.puts count
1
:ok
iex(3)> count = 2
2
iex(4)> IO.puts count
2
:ok
在 elixir 中,值是不可变的,但我可以让变量指向内存中存储其他值的不同位置。例如,当该行执行时,2 存储在内存中的某个位置,然后绑定到该新内存位置。之后,没有变量绑定到内存位置 1,以便内存已准备好进行垃圾回收。count = 2
count
Elixir 具有闭包,因为函数在定义函数的环境中确实带有绑定,但绑定是指向特定内存位置的,并且这些内存位置的值是不可变的:
defmodule A do
def counter do
count = 0
fn ->
count = count + 1
count
end
end
end
在 iex 中:
iex(6)> c("a.ex")
[A]
iex(7)> counter = A.counter
#Function<0.55300721/0 in A.counter/0>
iex(8)> counter.()
1
iex(9)> counter.()
1
iex(10)> counter.()
1
我可以把它做成一个单元素列表,但这听起来像是一个坏主意™。
...这是行不通的。列表是内存中不可变值的优势的一个例子。当您向列表的头部添加一个值时,Elixir 会在内存中的其他地方创建一个全新的列表。但是,Elixir 知道旧列表是不可变的,而不是将旧列表复制到新列表的内存位置,因此 elixir 可以只使用指向旧列表的指针。新的内存位置由列表的新元素和指向旧列表的指针组成,无需复制。在闭包的情况下,绑定将绑定到内存中原始列表的位置,对原始列表的任何更改都将存储在内存中的其他位置。
处理这种纯粹假设情况的灵丹妙药方法是什么?
在 elixir/erlang 中,您可以使用称为 GenServer 的东西来保留函数调用之间的状态:
defmodule Counter do
use GenServer
#Client interface:
def start_counter(starting_count) do
GenServer.start_link(__MODULE__, starting_count)
end
def get_count(pid) do
GenServer.call(pid, :increment)
end
# GenServer callback functions:
@impl true
def init(starting_count) do
{:ok, starting_count}
end
@impl true
def handle_call(:increment, _from, current_count) do
{:reply, current_count, current_count+1}
end
end
当你写:
GenServer.call(pid, :increment)
Elixir 会寻找一个名为其第一个参数匹配的回调函数并执行它,并将状态作为第三个参数传入。您可以定义执行所需的操作,然后将回复发送回调用进程并设置新状态。handle_call()
:increment
handle_call()
在 iex 中:
iex(1)> c("a.ex")
[Counter]
iex(2)> {:ok, pid} = Counter.start_counter(1)
{:ok, #PID<0.119.0>}
iex(3)> Counter.get_count(pid)
1
iex(4)> Counter.get_count(pid)
2
iex(5)> Counter.get_count(pid)
3
iex(6)> Counter.get_count(pid)
4
评论
GenServers
如果需要状态,请使用进程。在Elixir中,惯用的方法是使用Agent和GenServer。
代理文档中的示例完全按照您的要求执行:
defmodule Counter do
use Agent
def increment do
Agent.update(__MODULE__, &(&1 + 1))
end
# ...
end
用法:
Counter.start_link(0)
#=> {:ok, #PID<0.123.0>}
Counter.value()
#=> 0
Counter.increment()
#=> :ok
Counter.increment()
#=> :ok
Counter.value()
#=> 2
评论