Spec-Zone.ru › Elixir 1.7

Перечислимый протокол

Протокол перечислимого объекта, используемый модулями 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).

Этот протокол требует реализации четырёх функций: reduce/3, count/1, member?/2 и slice/1. Ядром протокола является функция reduce/3. Все остальные функции существуют как оптимизированные пути для структур данных, которые могут реализовать определённые свойства быстрее, чем за линейное время.

Краткое описание

Типы

acc()

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

continuation()

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

reducer()

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

result()

Результат операции сворачивания

slicing_fun()

Функция слайсинга, которая получает начальную позицию и количество элементов в слайсе

t()

Функции

count(enumerable)

Возвращает количество элементов в перечислимом объекте

member?(enumerable, element)

Проверяет, существует ли элемент в перечислимом объекте

reduce(enumerable, acc, fun)

Сворачивает перечислимый объект в элемент

slice(enumerable)

Возвращает функцию, которая выполняет срез данных последовательно

Типы

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 определена в рекурсивной форме.

reducer()

reducer() :: (term(), term() -> acc())

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

Должна вызываться с элементом перечислимого объекта и содержимым аккумулятора.

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

result()

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

Результат операции сворачивания.

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

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

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

slicing_fun()

slicing_fun() ::
  (start :: non_neg_integer(), length :: pos_integer() -> [term()])

Функция слайсинга, которая получает начальную позицию и количество элементов в слайсе.

Позиция start — это число >= 0 и гарантируется, что она существует в перечислимом объекте. Длина — это число >= 1 таким образом, что start + length <= count, где count — максимальное количество элементов в перечислимом объекте.

Функция должна вернуть список ненулевой длины, где количество элементов равно length.

t()

t() :: term()

Функции

count(enumerable)

Возвращает количество элементов в перечислимом объекте.

Должен вернуть {:ok, count} если вы можете подсчитать количество элементов в перечислимом объекте.

В противном случае он должен вернуть {:error, __MODULE__} и будет использоваться алгоритм по умолчанию, основанный на reduce/3, работающий за линейное время.

member?(enumerable, element)

Проверяет, существует ли элемент в перечислимом объекте.

Должен вернуть {:ok, boolean} если вы можете проверить членство заданного элемента в перечислимом объекте с помощью ===/2 без обхода всего перечислимого объекта.

В противном случае он должен вернуть {:error, __MODULE__} и будет использоваться алгоритм по умолчанию, основанный на reduce/3, работающий за линейное время.

reduce(enumerable, acc, fun)

Сворачивает перечислимый объект в элемент.

Большинство операций в Enum реализованы в терминах reduce. Эта функция должна применять предоставленную функцию reducer/0 к каждому элементу в перечислимом объекте и действовать в соответствии с ожидаемым значением возвращаемого аккумулятора.

См. документацию по типам 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)

Возвращает функцию, которая выполняет срез данных последовательно.

Должен вернуть {:ok, size, slicing_fun} если перечислимый объект имеет известную границу и может получить доступ к позиции в перечислимом объекте без обхода всех предыдущих элементов.

В противном случае он должен вернуть {:error, __MODULE__} и будет использован алгоритм по умолчанию, основанный на reduce/3, работающий за линейное время.

Отличия от count/1

Значение size возвращаемое этой функцией, используется для проверки границ, поэтому крайне важно, что эта функция возвращает :ok только если получение size перечислимого объекта происходит быстро и занимает постоянное время. В противном случае даже самые простые операции, такие как Enum.at(enumerable, 0), станут слишком дорогостоящими.

С другой стороны, функция count/1 в этом протоколе должна быть реализована всякий раз, когда вы можете подсчитать количество элементов в коллекции.

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.7.4/Enumerable.html

Spec-Zone.ru

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