Spec-Zone.ru › Elixir 1.9

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

Функции

count(enumerable)

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

member?(enumerable, element)

Проверяет, существует ли element в enumerable.

reduce(enumerable, acc, fun)

Сокращает enumerable до одного элемента.

slice(enumerable)

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

Типы

acc()

Specs

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

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

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

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

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

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

continuation()

Specs

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

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

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

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

reducer()

Specs

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

Функция редукции.

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

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

result()

Specs

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

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

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

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

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

slicing_fun()

Specs

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

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

Позиция start — это число >= 0 и гарантированно существует в enumerable.

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

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

t()

Specs

t() :: term()

Функции

count(enumerable)

Спецификации

count(t()) :: {:ok, non_neg_integer()} | {:error, module()}

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

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

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

member?(enumerable, element)

Спецификации

member?(t(), term()) :: {:ok, boolean()} | {:error, module()}

Проверяет, существует ли element в enumerable.

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

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

reduce(enumerable, acc, fun)

Спецификации

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)

Спецификации

slice(t()) ::
  {:ok, size :: non_neg_integer(), slicing_fun()} | {:error, module()}

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

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

В противном случае должно вернуть {: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.9.4/Enumerable.html

Spec-Zone.ru

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