javascript 在Elixir中使用闭包创建计数器

1dkrff03  于 12个月前  发布在  Java
关注(0)|答案(2)|浏览(63)

我正在学习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中是不可变的。我可以把它变成一个单元素列表,但这听起来像是一个坏主意。Elixir如何处理这种纯粹假设的情况?

n9vozmp4

n9vozmp41#

主要的问题似乎是count在Elixir中是不可变的。

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中,值是不可变的,但是变量可以指向内存中存储其他值的不同位置。例如,当行count = 2执行时,2被存储在内存中的某个位置,然后count被绑定到新的内存位置。之后,没有变量被绑定到1的内存位置,这样内存就可以进行垃圾收集了。
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


我可以把它做成一个单元素列表,但这听起来像是一个坏主意。
......但它不会起作用。列表是内存中不可变值的优势的一个例子。当你在列表的头部添加一个值时,elixer会在内存中的其他位置创建一个全新的列表。但是,elixer知道,旧列表是不可变的,而不是将旧列表复制到新列表的内存位置。因此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

bmvo0sr5

bmvo0sr52#

如果你需要状态,使用process。在Elixir中,惯用的方法是使用AgentGenServer
代理文档中的示例完全按照您的要求执行:

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

相关问题