Spec-Zone.ru › Elixir 1.14

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()

Результат операции reduce.

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()Source

@type acc() :: {:cont, term()} | {:halt, term()} | {:suspend, term()}

Значение аккумулятора на каждом шаге.

Это должен быть помеченный кортеж с одним из следующих «меток»:

  • :cont — перечисление должно быть продолжено
  • :halt — перечисление должно быть немедленно остановлено
  • :suspend — перечисление должно быть немедленно приостановлено

В зависимости от значения аккумулятора, результат, возвращаемый Enumerable.reduce/3, будет изменяться. Для получения дополнительной информации см. документацию типа result/0.

В случае, если функция reducer/0 возвращает :suspend аккумулятор, вызывающий код должен явно обработать его и никогда не должен его оставлять утечкой.

continuation()Source

@type continuation() :: (acc() -> result())

Частично применённая функция reduce.

Продолжение — это замыкание, возвращаемое в результате приостановки перечисления. При вызове оно ожидает новый аккумулятор и возвращает результат.

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

reducer()Source

@type reducer() :: (element :: term(), current_acc :: acc() -> updated_acc :: acc())

Функция-редуктор.

Должна вызываться с элементом enumerable и содержимым аккумулятора.

Возвращает аккумулятор для следующего шага перечисления.

result()Source

@type result() ::
  {:done, term()} | {:halted, term()} | {:suspended, term(), continuation()}

Результат операции reduce.

Он может быть завершён, когда перечисление закончено, достигнув своего конца, или остановлен/приостановлен, когда перечисление было остановлено или приостановлено помеченным аккумулятором.

В случае, если помеченный :halt аккумулятор предоставлен, должен быть возвращен кортеж :halted с аккумулятором. Функции, такие как Enum.take_while/2, используют :halt в основе и могут использоваться для тестирования останавливаемых перечисляемых объектов.

В случае, если помеченный :suspend аккумулятор предоставлен, вызывающий код должен вернуть кортеж :suspended с аккумулятором и продолжением. Вызывающий код несет ответственность за управление продолжением, и вызывающий код всегда должен вызвать продолжение, в конечном итоге останавливая или продолжая до конца. Enum.zip/2 использует приостановку, поэтому её можно использовать для проверки правильности обработки приостановки вашей реализацией. Также вы можете использовать Stream.zip/2 с Enum.take_while/2 для проверки сочетания :suspend с :halt.

slicing_fun()Source

@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()Source

@type t() :: term()

t(_element)Source

@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()Source

@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, не проходясь по всему списку.

В противном случае должно возвращать {: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 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.14.1/Enumerable.html

Spec-Zone.ru

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