Spec-Zone.ru › Elixir 1.18

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

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 без перебора всего enumerable.

В противном случае должно возвращать {: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.36.1) для языка программирования Elixir

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

Spec-Zone.ru

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