Источник Генераторы
В Elixir часто требуется итерироваться по Enumerable, часто отфильтровывая некоторые результаты и преобразуя значения в другой список. Генераторы — это синтаксический сахар для таких конструкций: они объединяют эти распространённые задачи в for специальную форму.
Например, мы можем преобразовать список целых чисел в список их квадратов:
iex> for n <- [1, 2, 3, 4], do: n * n [1, 4, 9, 16]
Генератор состоит из трёх частей: генераторов, фильтров и собираемых элементов.
Генераторы и фильтры
В выражении выше n <- [1, 2, 3, 4] является генератором. Он буквально генерирует значения, которые будут использоваться в генераторе. Любой перечисляемый объект может быть передан в правой части выражения генератора:
iex> for n <- 1..4, do: n * n [1, 4, 9, 16]
Выражения генераторов также поддерживают сопоставление с образцом в левой части; все несоответствующие образцы игнорируются. Представьте, что вместо диапазона у нас есть список ключевых слов, где ключ — атом :good или :bad, и мы хотим вычислить квадрат только :good значений:
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
В качестве альтернативы сопоставлению с образцом можно использовать фильтры для выбора определённых элементов. Например, мы можем выбрать кратные 3 и отбросить все остальные:
iex> for n <- 0..5, rem(n, 3) == 0, do: n * n [0, 9]
Генераторы отбрасывают все элементы, для которых выражение фильтра возвращает false или nil; все остальные значения выбираются.
Генераторы обычно предоставляют гораздо более лаконичное представление, чем использование эквивалентных функций из модулей Enum и Stream. Кроме того, генераторы позволяют указать несколько генераторов и фильтров. Вот пример, который получает список каталогов и получает размер каждого файла в этих каталогах:
dirs = ["/home/mikey", "/home/james"]
for dir <- dirs,
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
File.stat!(path).size
end
Также можно использовать несколько генераторов для вычисления декартова произведения двух списков:
iex> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j}
[a: 1, a: 2, b: 1, b: 2, c: 1, c: 2]
Наконец, имейте в виду, что присваивания переменных внутри генератора, будь то в генераторах, фильтрах или внутри блока, не отражаются за пределами генератора.
Генераторы битовых строк
Также поддерживаются генераторы битовых строк, которые очень полезны, когда необходимо работать с потоками битовых строк. В примере ниже предоставляется список пикселей из двоичного файла с соответствующими значениями красного, зелёного и синего цветов, и преобразует их в кортежи по три элемента каждый:
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
Генератор битовых строк может быть смешан с «обычными» генераторами перечисляемых объектов и поддерживает фильтры.
Опция :into
В примерах выше все генераторы возвращали списки в качестве результата. Однако результат генератора можно вставить в другие структуры данных, передав опцию :into генератору.
Например, генератор битовых строк может использоваться с опцией :into для лёгкого удаления всех пробелов в строке:
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>> "helloworld"
Множества, карты и другие словари также могут быть переданы в опцию :into. В общем случае :into принимает любую структуру, которая реализует протокол Collectable.
Распространённым случаем использования :into может быть преобразование значений в карте:
iex> for {key, val} <- %{"a" => 1, "b" => 2}, into: %{}, do: {key, val * val}
%{"a" => 1, "b" => 4}
Давайте приведём ещё один пример с потоками. Поскольку модуль IO предоставляет потоки (которые являются одновременно Enumerable и Collectable), можно реализовать терминал эха, который возвращает строку в верхнем регистре, используя генераторы:
iex> stream = IO.stream(:stdio, :line) iex> for line <- stream, into: stream do ...> String.upcase(line) <> "\n" ...> end
Теперь введите любую строку в терминал, и вы увидите, что то же самое значение будет напечатано в верхнем регистре. К сожалению, в этом примере ваша оболочка IEx также застряла в генераторе, поэтому вам нужно нажать Ctrl+C дважды, чтобы выйти из него. :)
Другие опции
Генераторы поддерживают другие опции, такие как :reduce и :uniq. Вот дополнительные ресурсы для получения дополнительной информации о генераторах:
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/comprehensions.html