Elixir: Неизменяемые структуры данных
Эликсир, как и большинство других функциональных языков, основан на иммутабельности данных.
Польза иммутабельности:
- исключает ошибки, связанные с модификацией одной области памяти из разных мест в коде;
- в том числе из разных потоков, что существенно упрощает многопоточное программирование;
- упрощает сборку мусора;
- сохраняются все промежуточные версии данных, что упрощает отладку;
- компилятор имеет больше возможностей для оптимизации кода.
Плата за это -- несколько менее эффективные структуры данных, чем в императивных языках. Можно говорить, что в среднем производительность мутабельных и иммутабельных структур данных сопоставимая, но в крайних случаях производительность хуже, иногда существенно хуже.
Как это работает
Допустим, мы создали некую структуру данных, сохранили ее в некой области памяти, и присвоили как значение некой переменной. При этом в переменной сохраняется ссылка на эту область памяти.
Я могу передавать переменную в функцию. Значение передается по ссылке, а не копируется. Но функция не может изменить это значение. Так что передача по ссылке безопасна.
Эликсир гарантирует, что выделенная память не модифицируется. Но переменную, которая указывает на эту область памяти, можно изменить. То есть, указать на другую область памяти.
Для примера рассмотрим код:
my_list = [1, 2, 3]
# => [1, 2, 3]
my_list = my_list ++ [4, 5]
# => [1, 2, 3, 4, 5]
my_list = my_list ++ [6, 7, 8]
# => [1, 2, 3, 4, 5, 4, 5, 6, 7, 8]
Здесь мы имеем 3 списка, каждый из них размещен в своей области памяти. И здесь только одна переменная, которая сперва указывает на первый список, затем на второй, затем на третий.
Переиспользование памяти (Structure Sharing)
Любые данные нужно как-то модифицировать, иммутабельные данные не исключение. Если каждый раз при модификации мы будем создавать полную копию данных, то это будет очень не эффективно и по использованию памяти, и по использованию CPU.
Поэтому полного копирования не происходит. Вместо этого, при создании новой структуры данных BEAM переиспользует часть или всю старую структура. Иммутабельность позволяет это делать.
В примере выше, 3 списка совместно используют одну и ту же область памяти. А новая память выделяется только под новые элементы. Аналогично работает и Map.
my_map = %{a: 42}
# => %{a: 42}
other_map = Map.put(my_map, :b, 500)
# => %{a: 42, b: 500}
yet_another_map = Map.put(my_map, :c, 100500)
# => %{a: 42, c: 100500}
my_map
# => %{a: 42}
other_map
# => %{a: 42, b: 500}
yet_another_map
# => %{a: 42, c: 100500}
Задание
Реализуем шифр Цезаря -- простой способ шифрования путем сдвига каждого символа на константу.
Нужно реализовать функцию encode/2
, которая принимает строку и сдвиг, и возвращает зашифрованную строку.
Solution.encode('Hello', 10)
# => 'Rovvy'
Solution.encode('Hello', 5)
# => 'Mjqqt'
Также нужно реализовать функцию decode/2
, которая принимает зашифрованную строку и сдвиг, и возвращает оригинальную строку.
Solution.decode('Rovvy', 10)
# => 'Hello'
Solution.decode('Mjqqt', 5)
# => 'Hello'
Упражнение не проходит проверку — что делать? 😶
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя 🤔
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно 🙄
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.