Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Elixir
Code

Как работать со списковыми включениями в Elixir

by
Difficulty:IntermediateLength:MediumLanguages:

Russian (Pусский) translation by Aigul_Nab (you can also view the original English article)

Elixir - очень молодой язык программирования (появился в 2011), однако он начинает набирать популярность. Изначально этот язык заинтересовал меня потому, что позволяет под иным углом взглянуть на решение типичных задач, стоящих перед программистом. Например, он позволит вам понять, как выполнять обход коллекций без использования цикла for, или как организовать ваш код без использования классов.

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

Списковые включения и маппинг

Вообще говоря, списковые включения - специальная конструкция, которая позволяет создавать новые списки на основе существующих. Эта концепция встречается в таких языках как Haskell и Clojure. Есть она и в Erlang, поэтому Elixir также поддерживает списковые включения.

Вы можете спросить, чем списковые включения отличаются от функции map/2, которая также создает новую коллекцию на основе исходной. Хороший вопрос! В простейшем случае списковые включения делают то же самое. Рассмотрим следующий пример:

Здесь мы просто берём список с тремя числами и возвращаем новый список, в котором каждый элемент умножается на 2. Вызов функции map может быть упрощён: Enum.map( &(&1 * 2) ).

Функция do_something/1 может быть переписана с использованием спискового включения:

Так выглядит простое списковое включение. На мой взгляд, данный код выглядит несколько элегантнее, чем в первом примере. Здесь мы снова берём каждый элемент из списка и умножаем его на 2. Конструкция el <- list, которая описывает порядок извлечения элементов коллекции, называется генератор.

Обратите внимание, что мы не обязаны передавать список функции do_something/1 — этот код будет работать с любым перечислимым аргументом:

В этом примере в качестве аргумента я передаю диапазон.

Списковые включения также поддерживают двоичные строки. В этом случае синтаксис немного отличается, так как необходимо заключить генератор в угловые скобки << и >>. Продемонстрируем это на примере создания простой функции, которая "расшифровывает" строку, защищенную шифром Цезаря. Идея проста: мы заменяем каждую букву в слове другой буквой, находящейся на несколько позиций правее неё в алфавите. Для простоты установим значение сдвига в 1.

Это очень похоже на предыдущий пример за исключением << и >>. Мы берём числовой код каждого символа в строке, уменьшаем его на единицу и воссоздаем строку. Таким образом, зашифрованным сообщением было "elixir"!

Однако, это еще далеко не всё. Другая полезная особенность списковых включений — возможность отфильтровывать некоторые элементы.

Списковые включения и фильтрация

Давайте немного усложним наш изначальный пример. Мы передадим диапазон целых чисел от 1 до 20, возьмем только четные элементы и умножим их на 2.

Здесь мне потребовалось подключить модуль Integer, чтобы использовать макрос is_even/1. Кроме того, я использую Stream, чтобы немного оптимизировать код и не выполнять итерацию дважды.

Теперь давайте перепишем пример с использованием списковых включений:

Как видите, for может принимать дополнительный фильтр, чтобы пропускать некоторые элементы коллекции.

Вы не ограничены одним фильтром, поэтому следующий код также корректен:

Эта функция выберет все чётные числа меньше 10. Только не забудьте разделить фильтры запятыми.

Фильтры будут запущены для каждого элемента коллекции; если возвращается true, то выполняется блок. В противном случае берётся новый элемент. Что интересно, генераторы также могут быть использованы для фильтрации элементов с помощью when:

Это очень похоже на написание граничных операторов:

Списковые включения при работе с несколькими коллекциями

Предположим, у нас есть одновременно две коллекции, на основе которых мы хотим создать новую. Например, возьмём все чётные числа из первой коллекции, затем нечётные из второй и перемножим их:

Этот пример показывает, как списковые включения могут одновременно работать с несколькими коллекциями. Выбирается первое чётное число из collection1 и перемножается с каждым нёчетным числом из collection2. Далее выбирается и перемножается второе чётное число из collection1,  и так далее. В результате получится:

Кроме того, итоговые значения не обязательно должны быть целыми. К примеру, вы можете вернуть кортеж, содержащий числа из первой и второй коллекций:

Списковые включения с опцией "Into"

До сих пор выходное значение работы списковых включений было представлено в виде списка. На самом деле, это не обязательно. Вы можете указать параметр into, принимающий коллекцию, которая будет содержать конечный результат.

Этот параметр принимает любую структуру, которую реализует протокол Collectable. Например, таким образом мы можем выполнить маппинг:

Здесь я просто указал into: Map.new, что может быть заменено на конструкцию into: %{}. Возвращая кортеж {el1, el2}, мы фактически указываем первый элемент в качестве ключа, а второй как значение.

Этот пример, однако, не очень полезен, поэтому давайте сгенерируем ассоциативный массив (map) с числом в качестве ключа и его квадратным корнем в качестве значения:

В этом примере я напрямую использовал модуль :math из языка Erlang, так как фактически названия всех модулей являются атомами. Теперь вы легко можете найти квадратный корень любого числа от 1 до 20.

Списковые включения и сопоставление по образцу

Последнее, что необходимо упомянуть: вы можете использовать сопоставление по образцу в списковых включениях. В некоторых случаях это может быть полезно.

Допустим, у нас есть ассоциативный массив, содержащий имена сотрудников и их зарплаты до налогового вычета:

Я хочу сгенерировать новый ассоциативный массив, в котором имена записаны строчными буквами и конвертированы в атомы, а зарплаты пересчитаны с учётом налогового вычета:

В этом примере мы задали атрибут модуля @tax с произвольным числом. Затем мы выполняем декомпозицию данных, используя конструкцию {name, salary} <- collection. И, наконец, форматируем имя и высчитываем зарплату, а затем сохраняем результат в новом ассоциативном массиве. Просто и выразительно.

Заключение

В этой статье мы рассмотрели использование списковых включений в Elixir. Вам может понадобиться некоторое время, чтобы привыкнуть к ним. Эта конструкция весьма лаконична и в некоторых случаях может подойти значительно лучше, чем функции map и filter. Вы найдете больше примеров в официальной документации Elixir и в руководстве для начинающих.

Я надеюсь, эта статья была полезна и интересна. Спасибо за внимание и до новых встреч!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.