In this series of posts we are gooing to look under the hood of Enum module. Here are the source and tests of the module.
Today our method under research is ubiquitous map. Let's go ahead, open iex and type h Enum.map .
iex> h Enum.map
Help system shows that the method has two signatures. The first one works with lists and the second one with dictionaries :
┃ iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
┃ [2, 4, 6]
┃
┃ iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
┃ [a: -1, b: -2]
Here is the source:
@doc """
Returns a new collection, where each item is the result
of invoking `fun` on each corresponding item of `collection`.
For dicts, the function expects a key-value tuple.
## Examples
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
iex> Enum.map([a: 1, b: 2], fn({k, v}) -> {k, -v} end)
[a: -1, b: -2]
"""
@spec map(t, (element -> any)) :: list
def map(collection, fun) when is_list(collection) do
for item <- collection, do: fun.(item)
end
def map(collection, fun) do
reduce(collection, [], R.map(fun)) |> :lists.reverse
end
As we see the method has a guard when is_list, which means that it is not possible to pass something other than list. Otherwise pattern matching is not going to be happy.
Under the hood the map method uses comprehensions:
for item <- collection, do: fun.(item)
Comprehensions are syntactic sugar for manipulating lists.A comprehension is made of three parts: generators, filters and collectables. In our particular case item<-collection is a generator. fun.(item) is an anonymous function applied to any item within collection.
Before I start reading code I usually think of how my own naive implementation of the feature would look like. This time I've came to the following conclusion:
defmodule MyEnum do
def map([h|t],fun), do: [fun.(h)| map(t,fun)]
def map([],_), do: []
end