在Elixir中创建闭包

use*_*857 10 closures elixir

我有一个Sequence结构,由一个state和一个generator函数组成,该函数从旧的生成新状态.我想写一个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
Run Code Online (Sandbox Code Playgroud)

我想得到以下结果:

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

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

PS:我不允许在结构中添加新字段,我不想使用GenServer和之类的东西ETS

Ale*_*kin 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
Run Code Online (Sandbox Code Playgroud)

好的,让我们测试一下:

iex|2 ? Test.closure(2).(2)  
2
1
NIL
:ok
Run Code Online (Sandbox Code Playgroud)

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

st ->
  IO.puts(n) # no closure happens here
  n = n - 1
  generator.(st)
Run Code Online (Sandbox Code Playgroud)

该条款中的第二行完全没有效果,因为Elixir 中的所有内容都是不可变的.n = n - 1将局部变量n恢复为新值,但是在generator接收后立即丢弃(GC'd),因为接收st并且n不再在任何地方使用.

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

就像是

generator.(n, create_limit_gen(generator, n - 1))
Run Code Online (Sandbox Code Playgroud)

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