Почему этот код Elixir считается повторным связыванием

Вот отредактированная версия образца стека, которую я вижу на https://blog.codeship.com/statefulness-in-elixir/ (автор - Мика Вудс). Это работает, кстати.

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 ответа

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]

Это просто повторная привязка переменной. Здесь не происходит ничего изменчивого.

Это ошибочная особенность Эликсира, которая устарела. Если вы попытаетесь скомпилировать это, вы получите предупреждение о том, что переменная небезопасна. Это должно быть полностью удалено в ближайшее время. К сожалению, я не знаю точно, когда.

** РЕДАКТИРОВАТЬ ** Джастин Вуд ответ правильный. Если кому-то интересно, вот правильный способ кодирования этого. Верните значение, которое вы хотите использовать за пределами блока приема.

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 **

Вот более подробное объяснение.

Переменные эликсира не "назначены". Вместо этого они связаны со значением. Например, образец соответствия stack = [] привязывает пустой список к переменной stack, Если вы тогда делаете stack = [1 | stack], список [1] связан с переменной stack (повторная привязка, так как она уже была связана).

В первой части вашего ОП вы связывали stack в последних двух пунктах, но не в первом. Кстати, привязка переменной происходит каждый раз, когда вы сопоставляете уже привязанное значение с другим значением (если вы не используете пин-код ^ оператор).

Путаница возникает из-за использования негигиенических переменных в некоторых конструкциях эликсира, таких как if, case, cond, receiveи т. д. блоки, позволяя их переменным привязкам просачиваться за пределы блока. По той или иной причине, именно так был разработан Elixir, и эта "особенность" часто использовалась, как вы это делали в своем OP. Позже было решено, что это нежелательная функция, поэтому она устарела. В будущем он будет удален (я работаю так же, как замыкания).

Таким образом, проблема заключается не в повторном связывании, а в привязке переменной, просачивающейся за пределы блока.

Таким образом, когда эта "особенность" будет окончательно удалена, второй пример кода вашего OP должен вызвать ошибку компиляции, указывающую, что stack2 не связан. Следует также поднять предупреждения о том, что stack2 не используется в пунктах 2 и 3 вашего блока приема.

Как только вы поймете, что привязки переменных просачиваются из блока, я надеюсь, что это уберет магию. Суть:

  • связывайте только временные ценности внутри блоков if, else, cond, case и receive.
  • если вам нужно использовать значение, вычисленное внутри одного из этих блоков, верните соответствие возвращаемому значению, чтобы вы могли использовать эту привязку позже в своем коде.

Наконец, для ответа на вопрос переменная привязка аналогична изменяемой переменной. На самом деле, нет. При повторном связывании данные, связанные с переменной, не изменяются. Создается новая копия данных, и имя переменной связывается с новыми данными. Старые данные и любые другие переменные, связанные с исходными данными, остаются без изменений. Конечно, как только нет переменных, оставленных привязанными к исходным данным, GC запускает их для освобождения.

Другие вопросы по тегам