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 underneath 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 cons'ing the
element with an accumulated list.
Internally, Enum.map/2
is implemented as follows:
def map(enum, fun) do
reducer = fn x, acc -> {:cont, [fun.(x)|acc]} end
Enumerable.reduce(enum, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
Notice the user given function is wrapped into a reducer
function.
The reducer
function must return a tagged tuple after each step,
as described in the acc/0
type.
The reason the accumulator requires a tagged tuple is to allow the reducer function to communicate to the underlying enumerable the end of enumeration, allowing any open resource to be properly closed. It also allows suspension of the enumeration, which is useful when interleaving between many enumerables is required (as in zip).
Finally, Enumerable.reduce/3
will return another tagged tuple,
as represented by the result/0
type.