在Elixir中创建闭包

时间:2017-04-02 22:31:29

标签: closures elixir

我有一个Sequence结构,由stategenerator函数组成,它从旧函数生成新状态。我想编写一个limit函数,该函数返回一个新序列,该序列应该在最大值时返回新状态n次,并且每n + k次返回nil。到目前为止的代码是:

defmodule Sequence do
  defstruct [:state, :generator]

  def generate(%Sequence{state: nil}) do
    nil
  end

  def generate(%Sequence{state: state , generator: generator } = seq) do
    {old_state, new_state} = generator.(state) 
    { old_state,  %Sequence{ seq | state: new_state } }
  end

  def limit(%Sequence{ generator: generator } = seq, n) when n > -1 do
    lim_gen = create_limit_gen(generator, n)
    %Sequence{ seq | generator: lim_gen }
  end

  defp create_limit_gen(generator, n) do
    lim_gen = fn 
                  nil -> 
                    nil
                  _ when n == 0 -> 
                    nil
                  st ->
                    IO.puts(n) # no closure happens here
                    n = n - 1
                    generator.(st)
              end
    lim_gen
  end

end

我希望得到以下结果:

iex> seq = %Sequence{state: 0, generator: &{&1, &1 + 1}} |> Sequence.limit 2
iex> {n, seq} = seq |> Sequence.generate; n
0
iex> {n, seq} = seq |> Sequence.generate; n
1
iex> seq |> Sequence.generate
nil
iex> seq = %Sequence{state: 0, generator: &{&1, nil}} |> Sequence.limit 2
iex> {n, seq} = seq |> Sequence.generate; n
0
iex> seq |> Sequence.generate
nil

问题是IO.puts打印始终是相同的数字,这意味着它不会改变。但是我的限制生成器依赖于该值并且它在闭包中发生变化。这是什么问题,我该如何解决?欢迎任何帮助:)

PS:我不允许在结构中添加新字段,我也不想使用GenServerETS

之类的内容

2 个答案:

答案 0 :(得分:7)

在大多数情况下,创建一个MCVE来定位问题并了解正在发生的事情是有意义的。我们这样做:

iex|1 ▶ defmodule Test do
...|1 ▶   def closure(n) do
...|1 ▶     fn  
...|1 ▶       _ when is_nil(n) or n == 0 -> IO.puts("NIL")
...|1 ▶       _ ->
...|1 ▶         IO.puts(n)
...|1 ▶         closure(n - 1).(n - 1) # or something else
...|1 ▶     end 
...|1 ▶   end 
...|1 ▶ end

好的,让我们测试一下:

iex|2 ▶ Test.closure(2).(2)  
2
1
NIL
:ok

酷,它按预期工作。现在让我们回到你的代码:

st ->
  IO.puts(n) # no closure happens here
  n = n - 1
  generator.(st)

条款中的第二行完全没有效果,因为 Elixir 中的所有内容都是不可变的。 n = n - 1将局部变量n反弹到新值,但是在generator收到st并且未使用n后,它立即被删除(GC'd)在任何地方。

代码非常繁琐,但我建议您不需要在n中累积当前的create_limit_gen,您目前看到的正是闭包的工作方式:n是在创建闭包时分配一次,并且它不会随时间变化。要改变它,应该明确地改变它,例如,通过n传递(如我在MCVE的第一个片段中所示)。

这样的东西
generator.(n, create_limit_gen(generator, n - 1))

正确处理结果应该可以解决问题。

答案 1 :(得分:2)

我不确定你想要完成什么,但我认为这样做会更好:

def limit(seq, n) when n > -1 do
  %Sequence{state: {seq.state, n}, generator: create_limit_gen(seq.generator)}
end

defp create_limit_gen(generator) do
  lim_gen = fn
    {nil, _} ->
      nil 
    {_, 0} -> 
     nil
    {state, n} ->
      {old_state, new_state} = generator.(state)
      {old_state, {new_state, n}}
  end
  lim_gen
end