Spec-Zone.ru › Elixir 1.18

Исходный код Перечислимые объекты и потоки

Хотя Elixir позволяет нам писать рекурсивный код, большинство операций, которые мы выполняем над коллекциями, выполняется с помощью модулей Enum и Stream. Давайте узнаем как.

Перечислимые объекты

Elixir предоставляет понятие перечислимых объектов и модуль Enum для работы с ними. Мы уже изучили два перечислимых объекта: списки и карты.

iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]

Модуль Enum предоставляет большой набор функций для преобразования, сортировки, группировки, фильтрации и извлечения элементов из перечислимых объектов. Это один из модулей, которые разработчики часто используют в своём коде Elixir. Для общего обзора всех функций модуля Enum см. справочник Enum.

Elixir также предоставляет диапазоны (см. Range), которые также являются перечислимыми объектами:

iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6

Функции модуля Enum ограничены, как следует из названия, перечислением значений в структурах данных. Для специфических операций, таких как вставка и обновление определённых элементов, может потребоваться обращение к модулям, специфичным для типа данных. Например, если вы хотите вставить элемент в заданную позицию в списке, используйте функцию List.insert_at/3, так как вставка значения, например, в диапазон, не имеет смысла.

Мы говорим, что функции модуля Enum полиморфны, потому что они могут работать с различными типами данных. В частности, функции модуля Enum могут работать с любым типом данных, который реализует протокол Enumerable. Мы обсудим протоколы в последующих главах, а сейчас перейдём к определённому виду перечислимого объекта, называемому потоком.

Жадные и ленивые вычисления

Все функции в модуле Enum являются жадными. Многие функции ожидают перечислимый объект и возвращают список:

iex> odd? = fn x -> rem(x, 2) != 0 end
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]

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

iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum()
7500000000

Приведённый выше пример имеет цепочку операций. Мы начинаем с диапазона, затем умножаем каждый элемент в диапазоне на 3. Эта первая операция создаст и вернёт список с 100_000 элементами. Затем мы оставляем все нечётные элементы из списка, создавая новый список, теперь с 50_000 элементами, и затем суммируем все элементы.

Оператор конвейера

Символ |> в приведённом выше фрагменте — это оператор конвейера: он берёт результат выражения слева и передаёт его в качестве первого аргумента вызова функции справа. Его цель — выделить данные, преобразуемые последовательностью функций. Чтобы увидеть, как он может сделать код чище, взгляните на пример выше, переписанный без использования оператора |>:

iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000

Подробнее об операторе конвейера можно узнать из документации.

Потоки

В качестве альтернативы Enum, Elixir предоставляет модуль Stream, который поддерживает ленивые операции:

iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum()
7500000000

Потоки — это ленивые, составные перечислимые объекты.

В приведённом выше примере 1..100_000 |> Stream.map(&(&1 * 3)) возвращает тип данных, фактический поток, который представляет map вычисление над диапазоном 1..100_000:

iex> 1..100_000 |> Stream.map(&(&1 * 3))
#Stream<[enum: 1..100000, funs: [#Function<34.16982430/1 in Stream.map/2>]]>

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

iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?)
#Stream<[enum: 1..100000, funs: [...]]>

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

Многие функции модуля Stream принимают любой перечислимый объект в качестве аргумента и возвращают поток в качестве результата. Он также предоставляет функции для создания потоков. Например, Stream.cycle/1 можно использовать для создания потока, который бесконечно циклирует заданный перечислимый объект. Будьте осторожны, не применяйте функцию, такую как Enum.map/2, к таким потокам, так как они будут циклиться бесконечно:

iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.unfold/2>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]

Ещё одна интересная функция — Stream.resource/3, которую можно использовать для оборачивания ресурсов, гарантируя, что они открываются непосредственно перед перечислением и закрываются после него, даже в случае ошибок. Например, File.stream!/1 основана на Stream.resource/3 для потоковой обработки файлов:

iex> "path/to/file" |> File.stream!() |> Enum.take(10)

Приведённый выше пример извлечёт первые 10 строк выбранного вами файла. Это означает, что потоки могут быть очень полезны для обработки больших файлов или даже медленных ресурсов, таких как сетевые ресурсы.

Модули Enum и Stream предоставляют широкий набор функций, но вам не нужно знать все их наизусть. Ознакомьтесь с Enum.map/2, Enum.reduce/3 и другими функциями с map или reduce в их названиях, и вы естественным образом сформируете интуицию относительно наиболее важных случаев использования. Вы можете сначала сосредоточиться на модуле Enum и перейти к модулю Stream только в тех случаях, когда требуется ленивость, для работы с медленными ресурсами или большими, возможно, бесконечными, коллекциями.

Далее мы рассмотрим центральную для Elixir функцию — процессы, которые позволяют нам писать конкуретное, параллельное и распределённое программы лёгким и понятным способом.

← Предыдущая страница Рекурсия
Следующая страница → Процессы

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

Создано с помощью ExDoc (v0.36.1) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/enumerable-and-streams.html

Spec-Zone.ru

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