我有一个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
我希望得到以下结果:
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:我不允许在结构中添加新字段,我也不想使用GenServer
和ETS
答案 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