Перечислимый протокол
Протокол перечислимого, используемый модулями Enum и Stream.
Когда вы вызываете функцию в модуле Enum, первый аргумент обычно является коллекцией, которая должна реализовывать этот протокол. Например, выражение:
Enum.map([1, 2, 3], &(&1 * 2))
вызывает Enumerable.reduce/3 для выполнения операции сворачивания, которая строит сопоставленный список, вызывая функцию сопоставления &(&1 * 2) для каждого элемента в коллекции и потребляя элемент с накопленным списком.
Внутри Enum.map/2 реализуется следующим образом:
def map(enum, fun) do
reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
Enumerable.reduce(enum, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end Обратите внимание, что функция, предоставленная пользователем, обернута в функцию reducer/0. Функция reducer/0 должна возвращать помеченный кортеж после каждой итерации, как описано в типе acc/0.
Причина, по которой накопитель требует помеченного кортежа, заключается в том, чтобы позволить функции reducer/0 сообщить об окончании перечисления основному перечисляемому объекту, позволяя корректно закрыть любые открытые ресурсы. Это также позволяет приостановить перечисление, что полезно при необходимости чередования между многими перечисляемыми объектами (как в zip).
Наконец, Enumerable.reduce/3 вернёт другой помеченный кортеж, представленный типом result/0.
Краткое описание
Типы
- acc()
-
Значение накопителя на каждом шаге
- continuation()
-
Частично применённая функция reduce
- reducer()
-
Функция reducer
- result()
-
Результат операции reduce
- t()
Функции
- count(enumerable)
-
Возвращает размер перечислимого объекта
- member?(enumerable, element)
-
Проверяет, существует ли элемент в перечислимом объекте
- reduce(enumerable, acc, fun)
-
Сворачивает перечислимый объект в элемент
Типы
acc()
acc() :: {:cont, term()} | {:halt, term()} | {:suspend, term()} Значение накопителя на каждом шаге.
Это должен быть помеченный кортеж с одним из следующих «меток»:
-
:cont- перечисление должно продолжить работу -
:halt- перечисление должно немедленно остановиться -
:suspend- перечисление должно быть немедленно приостановлено
В зависимости от значения накопителя, результат, возвращаемый Enumerable.reduce/3, будет меняться. Для получения дополнительной информации см. документацию по типу result/0.
В случае, если функция reducer/0 возвращает накопитель :suspend, он должен быть явно обработан вызывающей стороной и никогда не должен утечь.
continuation()
continuation() :: (acc() -> result())
Частично применённая функция reduce.
Продолжение — это замыкание, возвращаемое в результате приостановки перечисления. При вызове оно ожидает новый накопитель и возвращает результат.
Продолжение легко реализовать, если функция reduce определена в хвостовой рекурсии. Если функция хвостово-рекурсивна, все состояния передаются в качестве аргументов, поэтому продолжение просто будет частично применённой функцией reduce.
reducer()
reducer() :: (term(), term() -> acc())
Функция reducer.
Должна вызываться с элементом перечислимого объекта и содержимым накопителя.
Возвращает накопитель для следующего шага перечисления.
result()
result() ::
{:done, term()} |
{:halted, term()} |
{:suspended, term(), continuation()} Результат операции reduce.
Он может быть завершен, когда перечисление завершается, достигнув своего конца, или остановлен/приостановлен, когда перечисление было остановлено или приостановлено функцией reducer/0.
В случае, если функция reducer/0 возвращает накопитель :suspend, кортеж :suspended должен быть явно обработан вызывающей стороной и никогда не должен утечь. На практике это означает, что обычные функции перечисления должны только заботиться о результатах :done и :halted.
Кроме того, вызов :suspend всегда должен сопровождаться другим вызовом, в конечном итоге останавливая или продолжая перечисление до конца.
t()
t() :: term()
Функции
count(enumerable)
count(t()) :: {:ok, non_neg_integer()} | {:error, module()} Возвращает размер перечислимого объекта.
Должен возвращать {:ok, size}.
Если возвращается {:error, __MODULE__}, используется алгоритм по умолчанию с использованием reduce и оператора соответствия (===). Этот алгоритм работает за линейное время.
Пожалуйста, используйте алгоритм по умолчанию, если не можете реализовать алгоритм, значительно более быстрый.
member?(enumerable, element)
member?(t(), term()) :: {:ok, boolean()} | {:error, module()} Проверяет, существует ли элемент в перечислимом объекте.
Должен возвращать {:ok, boolean}.
Если возвращается {:error, __MODULE__}, используется алгоритм по умолчанию с использованием reduce и оператора соответствия (===). Этот алгоритм работает за линейное время.
Пожалуйста, используйте алгоритм по умолчанию, если не можете реализовать алгоритм, значительно более быстрый.
reduce(enumerable, acc, fun)
reduce(t(), acc(), reducer()) :: result()
Сворачивает перечислимый объект в элемент.
Большинство операций в Enum реализованы с помощью reduce. Эта функция должна применять заданную функцию reducer/0 к каждому элементу в перечислимом объекте и продолжить ожидаемым образом, как указано возвращаемым накопителем.
В качестве примера, здесь представлена реализация reduce для списков:
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
def reduce([], {:cont, acc}, _fun), do: {:done, acc}
def reduce([h | t], {:cont, acc}, fun), do: reduce(t, fun.(h, acc), fun)
© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.4.5/Enumerable.html