为什么此Elixir代码计为重新绑定

时间:2017-06-15 18:30:59

标签: variables elixir dynamic-rebinding

以下是我在https://blog.codeship.com/statefulness-in-elixir/(Micah Woods)上看到的Stack示例的编辑版本。顺便说一下。

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默认值为stack,或者stack实际上是一个可变变量?

那么我如何才能正确理解Elixir中的重新绑定概念呢?在我看来,这是在可变变量领域的侵犯。重新绑定如何工作?

2 个答案:

答案 0 :(得分: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的一个错误特征已被弃用。如果您尝试编译它,您将收到一条警告,告诉您该变量不安全。这应该很快完全删除。不幸的是,我不确切知道何时。

答案 1 :(得分: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,但在第一个子句中没有重新绑定。顺便说一下,只要你将已绑定的值与不同的值匹配,就会发生变量重新绑定(假设你不使用^引脚)。

混淆来自在一些灵丹妙药构造中使用非卫生变量,如ifcasecondreceive等块,允许它们变量绑定到块外泄漏。出于这样或那样的原因,这就是Elixir最初设计的方式,以及这个"功能"经常像你在OP中那样被剥削。后来决定,这不是一个理想的功能,所以它被弃用了。在将来它将被删除(工作与我假设的闭包相同)。

所以,问题不是关于重新绑定,而是关于块外泄漏的变量绑定。

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

一旦你理解变量绑定从块中泄漏,我希望这将消除魔法。底线:

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

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