Spec-Zone.ru › Elixir 1.17

Исходный код 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()Исходный код

@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(), element_acc :: term() -> acc())

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

Должна вызываться с элементом 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, количество элементов для извлечения 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 в этом протоколе должна быть реализована всякий раз, когда вы можете посчитать количество элементов в коллекции без ее перебора.

Скачать версию ePub

Создано с помощью ExDoc (v0.34.1) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.17.2/Enumerable.html

Spec-Zone.ru

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