Эликсир, как и большинство других функциональных языков, основан на иммутабельности данных.
Польза иммутабельности:
Плата за это -- несколько менее эффективные структуры данных, чем в императивных языках. Можно говорить, что в среднем производительность мутабельных и иммутабельных структур данных сопоставимая, но в крайних случаях производительность хуже, иногда существенно хуже.
Допустим, мы создали некую структуру данных, сохранили ее в некой области памяти, и присвоили как значение некой переменной. При этом в переменной сохраняется ссылка на эту область памяти.
Я могу передавать переменную в функцию. Значение передается по ссылке, а не копируется. Но функция не может изменить это значение. Так что передача по ссылке безопасна.
Эликсир гарантирует, что выделенная память не модифицируется. Но переменную, которая указывает на эту область памяти, можно изменить. То есть, указать на другую область памяти.
Для примера рассмотрим код:
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 списка, каждый из них размещен в своей области памяти. И здесь только одна переменная, которая сперва указывает на первый список, затем на второй, затем на третий.
Любые данные нужно как-то модифицировать, иммутабельные данные не исключение. Если каждый раз при модификации мы будем создавать полную копию данных, то это будет очень не эффективно и по использованию памяти, и по использованию 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
, которая принимает набор символов (charlists
) и сдвиг, и возвращает зашифрованный набор символов (charlists
).
Solution.encode('Hello', 10)
# => 'Rovvy'
Solution.encode('Hello', 5)
# => 'Mjqqt'
Также нужно реализовать функцию decode/2
, которая принимает зашифрованную набор символов (charlists
) и сдвиг, и возвращает оригинальный набор символов (charlists
).
Solution.decode('Rovvy', 10)
# => 'Hello'
Solution.decode('Mjqqt', 5)
# => 'Hello'
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1defmodule Test do
2 use ExUnit.Case
3 import Solution
4
5 test "encode/decode" do
6 assert encode(~c"Hello", 10) |> decode(10) == ~c"Hello"
7 assert encode(~c"12345", 1) == ~c"23456"
8 assert decode(~c"12345", 1) == ~c"01234"
9 assert encode(~c"abcdef", 2) == ~c"cdefgh"
10 assert decode(~c"abcdef", 2) == ~c"_`abcd"
11 end
12
13 test "encode/decode with cyrillic symbols" do
14 assert encode(~c"Привет", 10) |> decode(10) == ~c"Привет"
15 assert encode(~c"Привет мир", 500) |> decode(500) == ~c"Привет мир"
16 end
17end
18
Решение учителя откроется через: