Enumerable protocol used by Enum and Stream modules.
When you invoke a function in the Enum module, the first argument
is usually a collection that must implement this protocol.
For example, the expression Enum.map([1, 2, 3], &(&1 * 2))
invokes Enumerable.reduce/3 to perform the reducing operation that
builds a mapped list by calling the mapping function &(&1 * 2) on
every element in the collection and consuming the element with an
accumulated list.
Note that the user-supplied function is wrapped into a reducer/0 function.
The reducer/0 function must return a tagged tuple after each step,
as described in the acc/0 type. At the end, Enumerable.reduce/3
returns result/0.
This protocol uses tagged tuples to exchange information between the
reducer function and the data type that implements the protocol. This
allows enumeration of resources, such as files, to be done efficiently
while also guaranteeing the resource will be closed at the end of the
enumeration. This protocol also allows suspension of the enumeration,
which is useful when interleaving between many enumerables is required
(as in the zip/1 and zip/2 functions).
This protocol requires four functions to be implemented, reduce/3,
count/1, member?/2, and slice/1. The core of the protocol is the
reduce/3 function. All other functions exist as optimizations paths
for data structures that can implement certain properties in better
than linear time.
Default implementation for lists
Sometimes you may want to implement this protocol for a list contained
in struct. This can be done by delegating to the Enumerable.List module
in the reduce/3 implementation and providing a straight-forward
implementation for the remaining ones:
It must be a tagged tuple with one of the following "tags":
:cont - the enumeration should continue
:halt - the enumeration should halt immediately
:suspend - the enumeration should be suspended immediately
Depending on the accumulator value, the result returned by
Enumerable.reduce/3 will change. Please check the result/0
type documentation for more information.
In case a reducer/0 function returns a :suspend accumulator,
it must be explicitly handled by the caller and never leak.
The continuation is the closure returned as a result when
the enumeration is suspended. When invoked, it expects
a new accumulator and it returns the result.
A continuation can be trivially implemented as long as the reduce
function is defined in a tail recursive fashion. If the function
is tail recursive, all the state is passed as arguments, so
the continuation is the reducing function partially applied.
It may be done when the enumeration is finished by reaching
its end, or halted/suspended when the enumeration was halted
or suspended by the tagged accumulator.
In case the tagged :halt accumulator is given, the :halted tuple
with the accumulator must be returned. Functions like Enum.take_while/2
use :halt underneath and can be used to test halting enumerables.
In case the tagged :suspend accumulator is given, the caller must
return the :suspended tuple with the accumulator and a continuation.
The caller is then responsible of managing the continuation and the
caller must always call the continuation, eventually halting or continuing
until the end. Enum.zip/2 uses suspension, so it can be used to test
whether your implementation handles suspension correctly. You can also use
Stream.zip/2 with Enum.take_while/2 to test the combination of
:suspend with :halt.
A slicing function that receives the initial position,
the number of elements in the slice, and the step.
The start position is a number >= 0 and guaranteed to
exist in the enumerable. The length is a number >= 1
in a way that start + length * step <= count, where
count is the maximum amount of elements in the enumerable.
The function should return a non empty list where
the amount of elements is equal to length.
Most of the operations in Enum are implemented in terms of reduce.
This function should apply the given reducer/0 function to each
element in the enumerable and proceed as expected by the returned
accumulator.
See the documentation of the types result/0 and acc/0 for
more information.
Examples
As an example, here is the implementation of reduce for lists:
Returns a function that slices the data structure contiguously.
It should return either:
{:ok, size, slicing_fun} - if the enumerable has a known
bound and can access a position in the enumerable without
traversing all previous elements. The slicing_fun will receive
a start position, the amount of elements to fetch, and a
step.
{:ok, size, to_list_fun} - if the enumerable has a known bound
and can access a position in the enumerable by first converting
it to a list via to_list_fun.
{:error, __MODULE__} - the enumerable cannot be sliced efficiently
and a default algorithm built on top of reduce/3 that runs in
linear time will be used.
The size value returned by this function is used for boundary checks,
therefore it is extremely important that this function only returns :ok
if retrieving the size of the enumerable is cheap, fast, and takes
constant time. Otherwise the simplest of operations, such as
Enum.at(enumerable, 0), will become too expensive.
On the other hand, the count/1 function in this protocol should be
implemented whenever you can count the number of elements in the collection
without traversing it.
You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.