Spec-Zone.ru › Elixir 1.15

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

Функция reduce.

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()Источник

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

Функция reduce.

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

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

result()Источник

@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()Источник

@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 без прохода по всему набору.

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

Spec-Zone.ru

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