0

这是我在https://blog.codeship.com/statefulness-in-elixir/上看到的 Stack 示例的编辑版本(由 Micah Woods 提供)。顺便说一句,它有效。

defmodule Stack do
  def start_link do
    pid = spawn_link(__MODULE__, :loop, [[]])
    {:ok, pid}
  end

  def loop(stack) do
    receive do
      {:size, sender} ->
        send(sender, {:ok, Enum.count(stack)})
      {:push, item} -> stack = [item | stack]
      {:pop, sender} ->
        [item | stack] = stack
        send(sender, {:ok, item})
    end
    loop(stack)
  end
end

loop()函数内部,stack变量在某些情况下会在receive块中反弹,但在其他情况下不会。这似乎是可变变量的行为,而不是变量重新绑定。

在我看来,只有在新旧变量之间有明确的界限时,才应该允许重新绑定变量。即只有当代码可以在没有变量重新绑定的情况下被重写。在没有变量重新绑定的语言中,loop()代码如下所示:

def loop(stack) do
  receive do
    {:size, sender} ->
      send(sender, {:ok, Enum.count(stack)})
      ###### stack2 not defined in this case ######
    {:push, item} -> stack2 = [item | stack]
    {:pop, sender} ->
      [item | stack2] = stack
      send(sender, {:ok, item})
  end
  loop(stack2)
end

注意stack2在第一种情况下没有定义。如果没有发生赋值,那么默认情况下被赋值,还是stack2实际上是一个可变变量?stackstack

那么我如何正确和合乎逻辑地理解 Elixir 中的这个重新绑定概念呢?在我看来,这正在侵犯可变变量领域。重新绑定如何在引擎盖下工作?

4

2 回答 2

1
iex(1)> stack = [1,2,3]
[1, 2, 3]
iex(2)> if false, do: [head | stack] = stack
nil
iex(3)> stack
[1, 2, 3]
iex(4)> if true, do: [head | stack] = stack
[1, 2, 3]
iex(5)> stack
[2, 3]

这只是变量的重新绑定。这里没有任何可变的事情发生。

这是 Elixir 的一个错误功能,已被弃用。如果您尝试编译它,您将收到一条警告,告诉您该变量不安全。这应该很快被完全删除。不幸的是,我不知道确切的时间。

于 2017-06-15T20:05:34.710 回答
0

** 编辑 ** 贾斯汀伍德的回答是正确的。如果有人感兴趣,这是编码的正确方法。返回要在接收块之外使用的值。

def loop(stack) do
  stack2 = receive do
    {:size, sender} ->
      send(sender, {:ok, Enum.count(stack)})
      ###### stack2 not defined in this case ######
      stack
    {:push, item} -> [item | stack]
    {:pop, sender} ->
      [item | stack2] = stack
      send(sender, {:ok, item})
      stack2
  end
  loop(stack2)
end

** 编辑 2 **

这里有更详细的解释。

Elixir 变量没有“赋值”。相反,它们被绑定到一个值。例如,模式匹配stack = []将一个空列表绑定到变量stack。如果您这样做stack = [1 | stack],则列表[1]将绑定到变量stack(重新绑定,因为它已经绑定)。

在您的 OP 的第一部分中,您stack在最后两个子句中重新绑定,但不是在第一个子句中。顺便说一句,只要您将已绑定的值与不同的值匹配(假设您不使用 pin^运算符),就会发生变量重新绑定。

混淆来自于在一些长生不老药构造中使用了不卫生的变量,如if, case, cond,receive等块,允许它们的变量绑定泄漏到块之外。出于某种原因,这就是 Elixir 最初的设计方式,并且这个“功能”经常被利用,就像您在 OP 中所做的那样。后来决定,这不是一个理想的功能,所以它被弃用了。将来它将被删除(与我假设的关闭相同)。

所以,问题不在于重新绑定,而在于变量绑定泄漏到块外。

因此,当最终删除这是“功能”时,您的 OP 的第二个代码示例应该引发编译错误,指示 stack2 未绑定。它还应该引发stack2在接收块的第 2 和第 3 子句中未使用的警告。

一旦您了解变量绑定正在从块中泄漏出来,我希望这将消除魔力。底线:

  • 仅在 if、else、cond、case 和 receive 块内绑定临时贵重物品。
  • 如果您需要使用在这些块之一内计算的值,请在返回值上返回匹配项,以便稍后在代码中使用该绑定。

最后,要回答这个问题,变量重新绑定与可变变量相同。并不真地。重新绑定时,绑定到变量的数据不会改变。创建数据的新副本并绑定新数据的变量名。旧数据和绑定到原始数​​据的任何其他变量保持不变。当然,一旦没有与原始数据绑定的变量,GC 就会开始释放它。

于 2017-06-15T20:10:48.757 回答