Do you want to pick up from where you left of?
Take me there

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

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

Table of Contents

Основы

Часто списковые включения используются для краткой записи итераций по Enum и Stream. Начнём с простого примера:

iex> list = [1, 2, 3, 4, 5]
iex> for x <- list, do: x*x
[1, 4, 9, 16, 25]

Первое, что бросается в глаза — использование for и генератора. Что такое генератор? Генераторы это выражения вида x <- [1, 2, 3, 4], используемые в списковых включениях для генерации следующего значения.

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

# Keyword Lists
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]

# Maps
iex> for {k, v} <- %{"a" => "A", "b" => "B"}, do: {k, v}
[{"a", "A"}, {"b", "B"}]

# Binaries
iex> for <<c <- "hello">>, do: <<c>>
["h", "e", "l", "l", "o"]

Можно заметить, что работа генераторов основывается на сопоставлении с образцом — сравнении набора входных значений с переменной с левой стороны выражения. Если совпадение не будет найдено, значение проигнорируется:

iex> for {:ok, val} <- [ok: "Hello", error: "Unknown", ok: "World"], do: val
["Hello", "World"]

Можно использовать несколько генераторов как вложенные циклы:

iex> list = [1, 2, 3, 4]
iex> for n <- list, times <- 1..n do
...>   String.duplicate("*", times)
...> end
["*", "*", "**", "*", "**", "***", "*", "**", "***", "****"]

Чтобы подробнее рассмотреть, что происходит в цикле, воспользуемся IO.puts для вывода пар генерируемых значений:

iex> for n <- list, times <- 1..n, do: IO.puts "#{n} - #{times}"
1 - 1
2 - 1
2 - 2
3 - 1
3 - 2
3 - 3
4 - 1
4 - 2
4 - 3
4 - 4

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

Фильтры

Фильтры можно рассматривать как ограничители списковых включений. Если отфильтрованное значение возвращает false или nil, оно исключается из итогового списка. Пройдёмся по диапазону и оставим только чётные числа:

import Integer
iex> for x <- 1..10, is_even(x), do: x
[2, 4, 6, 8, 10]

Также как и генераторы, можно использовать сразу несколько фильтров. Для примера возьмём диапазон и исключим из него нечётные значения и числа, некратные трём:

import Integer
iex> for x <- 1..100,
...>   is_even(x),
...>   rem(x, 3) == 0, do: x
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

Использование :into

А что, если мы хотим получить что-то кроме списка? Опция :into позволяет нам сделать это! Как правило, :into принимает любую структуру, реализующую протокол Collectable.

Создадим ассоциативный массив из ключевого списка при помощи :into:

iex> for {k, v} <- [one: 1, two: 2, three: 3], into: %{}, do: {k, v}
%{one: 1, three: 3, two: 2}

Строка — перечисляемый тип, поэтому мы можем использовать списковые включения и :into для создания строк:

iex> for c <- [72, 101, 108, 108, 111], into: "", do: <<c>>
"Hello"

Вот и всё! Списковые включения — это лёгкий и краткий способ прохода по коллекциям.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!