Implementing Enum functions in Elixir

Mohammed Hewedy
4 min readJun 30, 2023
Photo by Kimberly Farmer on Unsplash

Years back, I was introduced to Scala language through the progfun1 course at Coursera. It was a very interesting course, and we learned the language by implementing some core features such as the map, reduce, etc functions during the course.

Recently I started exploring Elixir language (and Erlang consequently), and I wanted to apply the same way of learning.

So I decided to implement some of the Enum methods, namely map , reduce, count and filter functions on lists

Map is all about changing the values in an enumerable (e.g. list) by applying some function to it

Marble diagram for map function

It can be implemented as follows:

def map([head | tail], fun) do
[fun.(head) | map(tail, fun)]
end

def map([], fun), do: []

We define two functions, one that takes an empty list and return an empty list. (the base case)
The other one takes head and tail, and apply the function on the head, and prepends the output to the result of mapping the tail recursively. it is called as follows:

map([1, 2, 3], &(&1 * 2)) |> IO.inspect
# [2, 4, 6]

Reduce is all about reducing or collecting the values of an enumerable (e.g. list) into a single value by applying some function on each element on the list.

Marble diagram for reduce function

It can be implemented as follows:

def reduce([head | tail], acc, fun) do
acc = fun.(acc, head)
reduce(tail, acc, fun)
end

def reduce([], acc, _), do: acc

We define two functions, one that takes an empty list and the accumulative value and return the accumulative value. (the base case)

The other one takes 3 parameters, the list (as head and tail), an accumulative, and a function to apply to each element.

it is as straightforward as applying the function on the acc and the head and then passing the result to the reduce again along with the tail.

The point here is, instead of returning the accumulative value (as in map), we simply keep sending as args to the next recursive call.

And it will be called as follows:

reduce([1, 2 ,3], fn acc, x -> acc + x end) |> IO.puts
# 6

Count is all about counting the elements of an enumerable (e.g. list) that pass the trueness of a function.

Marble diagram for count function

It can be implemented as follows:

def count([head | tail], fun) do
if fun.(head) do
count(tail, fun) + 1
else
count(tail, fun)
end
end

We define two functions, one that takes an empty list and returns 0. (the base case)

The other one takes 2 parameters, the list (as head and tail), and a function to apply to each element.

it is as straightforward as applying the function on the head and based on the result it decides whether to add 1 to the count or not.

And it will be called as follows:

count([1, 2, 3, 10, 20, 100], fn x -> x >= 10 end) |> IO.puts
# 3

Filter is all about filtering the elements of an enumerable (e.g. list) that pass the trueness of a function.

Marble diagram for filter function

It can be implemented as follows:

def filter([head | tail], fun) do
if fun.(head) do
[head | filter(tail, fun)]
else
filter(tail, fun)
end
end

We define two functions, one that takes an empty list and an empty list. (the base case)

The other one takes 2 parameters, the list (as head and tail), and a function to apply to each element.

it is as straightforward as applying the function on the head and based on the result it decides whether to prepend the head to the remaining of the recursive call on the tail or not.

And it will be called as follows:

filter([1, 2, 3, 4], fn x -> rem(x, 2) == 0 end) |> IO.inspect
# [2, 4]

As you can see, implementations are simple and require just thinking functionally.

--

--