Spec-Zone.ru › Elixir 1.16

Source Генераторы

В Elixir часто используется циклический проход по перечислимому объекту, часто с фильтрацией результатов и преобразованием значений в другой список. Генераторы — это синтаксический сахар для таких конструкций: они группируют эти общие задачи в специальную форму 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. Вот дополнительные ресурсы для получения дополнительной информации о генераторах:

  • for официальная справка в документации Elixir
  • Полноценное руководство Митчелла Ханберга по генераторам Elixir
← Предыдущая страница Протоколы
Следующая страница → Сигилы

Скачать версию ePub

Создано с помощью ExDoc (v0.32.2) для Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/comprehensions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API