Что такое иммутабельность и как она реализована в Elixir?

В Elixir иммутабельность (неизменяемость) — это фундаментальное свойство всех данных. Это означает, что после создания значение переменной не может быть изменено. Вместо изменения существующего значения создаётся новая копия с учётом изменений. Это свойство заимствовано из языка Erlang, на котором основан Elixir, и поддерживается на уровне всей архитектуры языка и виртуальной машины BEAM.

Как реализована иммутабельность в Elixir:

Переменные — это связывания, а не контейнеры
В Elixir переменная — это просто имя, привязанное к значению. Она не может быть переприсвоена в рамках одного контекста. Пример:

x = 1
x = 2 # Это разрешено, потому что создаётся новое связывание x => 2, предыдущее x => 1 забывается

Однако в case, fn, for, if и других конструкциях нельзя переприсваивать значение, если оно уже связано в текущем шаблоне.

Структуры данных — только для чтения
Любая структура данных (кортежи, списки, мапы, строки, числа и т.д.) после создания не может быть изменена. Вместо этого создаётся новый объект:

list = \[1, 2, 3\]
new_list = \[0 | list\] # Новый список: \[0, 1, 2, 3\]

Нет циклической мутации данных
Нет механизма для прямого изменения ячеек памяти, как, например, в JavaScript или Python. Модификация данных всегда происходит через создание новых структур:

map = %{a: 1, b: 2}
updated = %{map | a: 5} # создаётся новый map, оригинал не меняется

Преимущества иммутабельности

    • Безопасность в многопоточности: нет гонок за доступ к памяти.

    • Упрощённая отладка и предсказуемость: переменная не может внезапно измениться.

    • Эффективность на уровне BEAM: несмотря на создание новых структур, виртуальная машина Elixir (через Erlang) оптимизирует память и работу с неизменяемыми объектами.

Garbage Collector и иммутабельность
BEAM эффективно очищает память от объектов, на которые больше не ссылается ни один процесс. Благодаря иммутабельности это безопасно: если данные больше не нужны, они точно не будут изменяться и могут быть удалены.

Иммутабельность в рекурсии и потоках
Поскольку в Elixir нет циклов while и for как в других языках, используется рекурсия и хвостовая рекурсия. Передача новых значений между итерациями возможна только путём возврата новых значений:

def sum(\[head | tail\], acc), do: sum(tail, acc + head)
def sum(\[\], acc), do: acc

Здесь acc — аккумулятор, который не изменяется, а создаётся заново на каждой итерации.

Иммутабельность и процессы
В Elixir каждый процесс изолирован. Общение между ними возможно только через сообщения. Благодаря иммутабельности передача данных безопасна — один процесс не может повлиять на память другого, даже если он получил ссылку на данные.

Сравнение с другими языками
В JavaScript, Python или Ruby переменные можно изменять, и это может привести к неожиданным ошибкам. В Elixir такой подход невозможен, потому что переменные нельзя изменить после связывания. Это делает программы более надёжными.

Отсутствие мутаций делает код более декларативным
Вместо того чтобы описывать, как что-то делать (как в императивных языках), вы описываете что нужно сделать, и результат — это новая структура. Например:

Enum.map([1, 2, 3], fn x -> x * 2 end)

  1. Здесь результат — новый список [2, 4, 6], исходный не изменяется.

  2. Связь с функциональным программированием
    Иммутабельность — основа функционального программирования, и Elixir строго придерживается этой парадигмы. Всё поведение Elixir-программ строится на чистых функциях, где нет побочных эффектов и переменных, изменяющих своё значение.