Исходный код Enumerable протокол
Протокол Enumerable, используемый модулями Enum и Stream.
При вызове функции в модуле Enum, первый аргумент обычно представляет собой коллекцию, которая должна реализовывать этот протокол. Например, выражение Enum.map([1, 2, 3], &(&1 * 2)) вызывает Enumerable.reduce/3 для выполнения операции сворачивания, которая строит массив преобразованных элементов, вызывая функцию преобразования &(&1 * 2) для каждого элемента в коллекции и используя накопленный список.
Внутренне, Enum.map/2 реализуется следующим образом:
def map(enumerable, fun) do
reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
Enumerable.reduce(enumerable, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
Обратите внимание, что функция, предоставленная пользователем, обернута в функцию reducer/0. Функция reducer/0 должна возвращать помеченную кортеж после каждого шага, как описано в типе acc/0. В конце Enumerable.reduce/3 возвращает result/0.
Этот протокол использует помеченные кортежи для обмена информацией между функцией сворачивания и типом данных, реализующим протокол. Это позволяет эффективно перебирать ресурсы, такие как файлы, гарантируя, что ресурс будет закрыт в конце перебора. Этот протокол также позволяет приостановить перебор, что полезно при необходимости чередования между многими перечислимыми объектами (как в функциях zip/1 и zip/2).
Для реализации этого протокола необходимо реализовать четыре функции: reduce/3, count/1, member?/2 и slice/1. Ядром протокола является функция reduce/3. Все остальные функции являются оптимизированными путями для структур данных, которые могут реализовывать определенные свойства за линейное время.
Краткое описание
Типы
- acc()
Значение аккумулятора на каждом шаге.
- continuation()
Частично примененная функция reduce.
- reducer()
Функция сворачивания.
- result()
Результат операции сворачивания.
- slicing_fun()
Функция слайсинга, которая принимает начальную позицию, количество элементов в слайсе и шаг.
- t()
Все типы, которые реализуют этот протокол.
- t(_element)
Перечислимый объект элементов типа
element.- to_list_fun()
Принимает перечислимый объект и возвращает список.
Функции
- count(enumerable)
Возвращает количество элементов в
enumerable.- member?(enumerable, element)
Проверяет, существует ли
elementвenumerable.- reduce(enumerable, acc, fun)
Сворачивает
enumerableв элемент.- slice(enumerable)
Возвращает функцию, которая выполняет слайсинг структуры данных.
Типы
acc()Исходный код
@type acc() :: {:cont, term()} | {:halt, term()} | {:suspend, term()} Значение аккумулятора на каждом шаге.
Это должна быть помеченная кортеж с одним из следующих "тегов":
-
:cont- перебор должен продолжаться -
:halt- перебор должен быть немедленно остановлен -
:suspend- перебор должен быть немедленно приостановлен
В зависимости от значения аккумулятора, результат, возвращаемый Enumerable.reduce/3, будет меняться. Дополнительную информацию см. в документации к типу result/0.
Если функция reducer/0 возвращает аккумулятор типа :suspend, он должен быть явно обработан вызывающей стороной и никогда не должен утечь.
continuation()Исходный код
@type continuation() :: (acc() -> result())
Частично примененная функция reduce.
Продолжение — это замыкание, возвращаемое в результате приостановления перебора. При вызове оно ожидает новый аккумулятор и возвращает результат.
Продолжение можно тривиально реализовать, если функция reduce определена рекурсивно в хвостовой части. Если функция рекурсивна в хвостовой части, все состояние передаётся в качестве аргументов, поэтому продолжение — это частично применённая функция reduce.
reducer()Исходный код
@type reducer() :: (element :: term(), current_acc :: acc() -> updated_acc :: acc())
Функция сворачивания.
Должна вызываться с элементом enumerable и содержимым аккумулятора.
Возвращает аккумулятор для следующего шага перебора.
result()Исходный код
@type result() ::
{:done, term()} | {:halted, term()} | {:suspended, term(), continuation()} Результат операции сворачивания.
Может быть завершен, когда перебор закончен, или остановлен/приостановлен, когда перебор был остановлен или приостановлен помеченным аккумулятором.
В случае передачи помеченного аккумулятора :halt, функция должна вернуть кортеж :halted с аккумулятором. Такие функции, как Enum.take_while/2, используют :halt и могут быть использованы для проверки останавливаемых перечислимых объектов.
В случае передачи помеченного аккумулятора :suspend, вызывающая функция должна вернуть кортеж :suspended с аккумулятором и продолжением. Затем вызывающая функция отвечает за управление продолжением, и она должна всегда вызывать продолжение, в конечном итоге останавливая или продолжая, пока не достигнет конца. Enum.zip/2 использует приостановку, поэтому её можно использовать для проверки того, правильно ли ваша реализация обрабатывает приостановку. Также можно использовать Stream.zip/2 с Enum.take_while/2 для проверки комбинации :suspend с :halt.
slicing_fun()Исходный код
@type slicing_fun() ::
(start :: non_neg_integer(), length :: pos_integer(), step :: pos_integer() ->
[term()]) Функция слайсинга, которая принимает начальную позицию, количество элементов в слайсе и шаг.
Позиция start — это число >= 0 и гарантируется, что она существует в enumerable. Длина — это число >= 1 таким образом, что start + length * step <= count, где count — максимальное количество элементов в перечислимом объекте.
Функция должна возвращать непустой список, где количество элементов равно length.
t()Исходный код
@type t() :: term()
Все типы, которые реализуют этот протокол.
t(_element)Исходный код
@type t(_element) :: t()
Перечислимый объект элементов типа element.
Этот тип эквивалентен t/0, но особенно полезен для документации.
Например, представьте, что вы определяете функцию, которая принимает перечислимый объект целых чисел и возвращает перечислимый объект строк:
@spec integers_to_strings(Enumerable.t(integer())) :: Enumerable.t(String.t()) def integers_to_strings(integers) do Stream.map(integers, &Integer.to_string/1) end
to_list_fun()Исходный код
@type to_list_fun() :: (t() -> [term()])
Принимает перечислимый объект и возвращает список.
Функции
count(enumerable)Source
@spec count(t()) :: {:ok, non_neg_integer()} | {:error, module()} Возвращает количество элементов в enumerable.
Должно вернуть {:ok, count} если вы можете подсчитать количество элементов в enumerable быстрее, чем с полным проходом.
В противном случае должно вернуть {:error, __MODULE__} и будет использован алгоритм по умолчанию, основанный на reduce/3, выполняющийся за линейное время.
member?(enumerable, element)Source
@spec member?(t(), term()) :: {:ok, boolean()} | {:error, module()} Проверяет, существует ли element в enumerable.
Должно вернуть {:ok, boolean} если вы можете проверить принадлежность заданного элемента в enumerable с помощью ===/2 без обхода всего enumerable.
В противном случае должно вернуть {:error, __MODULE__} и будет использован алгоритм по умолчанию, основанный на reduce/3, выполняющийся за линейное время.
Когда вызывается вне стражей, операторы in и not in работают с помощью этой функции.
reduce(enumerable, acc, fun)Source
@spec reduce(t(), acc(), reducer()) :: result()
Применяет функцию-редуктор к enumerable.
Большинство операций в Enum реализуются с помощью reduce. Данная функция должна применить указанную функцию-редуктор reducer/0 к каждому элементу в enumerable и продолжить как ожидается возвращаемым аккумулятором.
См. документацию типов result/0 и acc/0 для получения дополнительной информации.
Примеры
В качестве примера приведена реализация reduce для списков:
def reduce(_list, {: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([head | tail], {:cont, acc}, fun), do: reduce(tail, fun.(head, acc), fun) slice(enumerable)Source
@spec slice(t()) ::
{:ok, size :: non_neg_integer(), slicing_fun() | to_list_fun()}
| {:error, module()} Возвращает функцию, которая нарезает структуру данных непрерывно.
Должно вернуть:
{:ok, size, slicing_fun}- если уenumerableесть известная граница и она может получить доступ к позиции вenumerableбез обхода всех предыдущих элементов.slicing_funполучит позициюstart, количество элементов для извлеченияamountиstep.{:ok, size, to_list_fun}- если уenumerableесть известная граница и она может получить доступ к позиции вenumerable, сначала преобразовав её в список черезto_list_fun.{:error, __MODULE__}- структура данных не может быть эффективно нарезана и будет использован алгоритм по умолчанию, основанный наreduce/3, выполняющийся за линейное время.
Отличия от count/1
Значение size, возвращаемое этой функцией, используется для проверки границ, поэтому крайне важно, чтобы эта функция возвращала :ok только если получение size структуры enumerable является быстрым и занимает постоянное время. В противном случае даже самые простые операции, такие как Enum.at(enumerable, 0), станут слишком дорогостоящими.
С другой стороны, функция count/1 в этом протоколе должна быть реализована, когда вы можете посчитать количество элементов в коллекции без её обхода.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/Enumerable.html