Spec-Zone.ru › Elixir 1.16

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

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

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

Функция сворачивания.

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

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

result()Исходный код

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

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

Может быть завершен, когда перебор закончен, или остановлен/приостановлен, когда перебор был остановлен или приостановлен помеченным аккумулятором.

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

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

Spec-Zone.ru

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