I am trying to dissect and understand how the acc/0
and reducer/0
work within the Enumerable Protocol.
def map(enumerable, fun) do
reducer = fn x, acc -> {:cont, [fun.(x) | acc]} end
Enumerable.reduce(enumerable, {:cont, []}, reducer) |> elem(1) |> :lists.reverse()
end
Could someone explain this starting from the values of x, acc in each enumeration
This is almost but not quite like the higher-level Enum.reduce/3
, and similar "reduce" or "fold" operations in other functional languages. Enumerable
is a lower-level Elixir protocol (interface) that types can implement to say that they are "a container" and can be used with the Enum
function; reduce
is the core building block.
The reducer
parameter will get called once for each element in the list, with the previous (or initial) value of the accumulator as that parameter. The Enumerable:acc/0
type for the accumulator parameter includes not just the value but also a control atom that tells the Enumerable
implementation what to do, in particular with an option to stop early. :cont
here means "continue", and in this example you will always iterate through the entire list.
Let's say you call
map([1, 2, 3], &(&1 * 2))
Then the sequence of inner calls will be
reducer.(1, []) # {:cont, [2]}
reducer.(2, [2]) # {:cont, [4, 2]}
reducer.(3, [4, 2]) # {:cont, [6, 4, 2]}
and the remaining logic splits off the :cont
marker and reverses the list so it's in the correct order.