Spec-Zone.ru › Elixir 1.6

Протокол Enumerable protocol

Протокол 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).

Для реализации этого протокола необходимо четыре функции: reduce/3, count/1, member?/2 и slice/1. Ядром протокола является функция reduce/3. Все остальные функции являются оптимизированными путями для структур данных, которые могут реализовать определённые свойства лучше, чем за линейное время.

Краткое описание

Типы

acc()

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

continuation()

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

reducer()

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

result()

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

slicing_fun()

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

t()

Функции

count(enumerable)

Возвращает количество элементов в перечисляемом объекте

member?(enumerable, element)

Проверяет, существует ли элемент в перечисляемом объекте

reduce(enumerable, acc, fun)

Сводит перечисляемый объект к элементу

slice(enumerable)

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

Типы

acc()

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

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

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

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

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

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

continuation()

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

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

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

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

reducer()

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

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

Должна вызываться с элементом перечисляемого объекта и содержимым аккумулятора.

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

result()

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

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

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

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

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

slicing_fun()

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

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

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

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

t()

t() :: term()

Функции

count(enumerable)

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

Возвращает количество элементов в перечисляемом объекте.

Если вы можете посчитать количество элементов в перечисляемом объекте, оно должно возвращать {:ok, count}.

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

member?(enumerable, element)

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

Проверяет, существует ли элемент в перечисляемом объекте.

Если вы можете проверить членство данного элемента в перечисляемом объекте с === без прохода по всему перечисляемому объекту, оно должно возвращать {:ok, boolean}.

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

reduce(enumerable, acc, fun)

reduce(t(), acc(), reducer()) :: result()

Сводит перечисляемый объект к элементу.

Большинство операций в Enum реализованы с использованием reduce. Эта функция должна применять заданную функцию reducer/0 к каждому элементу перечисляемого объекта и действовать в соответствии с ожидаемым аккумулятором.

См. документацию типов result/0 и acc/0 для получения дополнительной информации.

Примеры

В качестве примера, вот реализация reduce для списков:

def reduce(_,       {: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([h | t], {:cont, acc}, fun),    do: reduce(t, fun.(h, acc), fun)

slice(enumerable)

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

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

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

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

Отличия от count/1

Значение size , возвращаемое этой функцией, используется для проверок границ, поэтому крайне важно, что эта функция возвращает :ok только в том случае, если получение size перечисляемого объекта является дешёвым, быстрым и занимает постоянное время. В противном случае самые простые операции, такие как Enum.at(enumerable, 0), станут слишком дорогими.

С другой стороны, функция count/1 в этом протоколе должна быть реализована всякий раз, когда вы можете посчитать количество элементов в коллекции.

© 2012 Plataformatec
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.6.6/Enumerable.html

Spec-Zone.ru

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