Elixir Enumerables

Welcome to another tutorial on Elixir. Here you will learn about Enumerables in Elixir.

In Elixir, an enumerable is an object that may be enumerated. "Enumerated" means to count off the members of a set/collection/category one after the other, usually in order, and by name.

Also, Elixir provides the concept of Enumerables and the Enum module to work with them. The functions in the Enum module are limited to, just as the name implies, enumerating values in data structures.

An example of an enumerable data structure is a list, tuple, map, and so on. Also, the Enum module provides us with a little over 100 functions to deal with enums. 

Note that all of these functions take an enumerable as the first element and a function as the second and work on them. These functions are briefly highlighted below.

 

all?

if we use them all? function, the entire collection must evaluate to true else, false will be returned. E.g. to check if all of the elements in a given list are odd numbers, we can do it like this: 

res = Enum.all?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end) 
IO.puts(res)

If the code is run, the output will be:

false

The output above implies that not all elements of this list are odd.

 

any?

In this case, the function returns true if any element of the collection evaluates to true. Check out the example below:

res = Enum.any?([1, 2, 3, 4], fn(s) -> rem(s,2) == 1 end)
IO.puts(res)

The output is:

true

 

chunk

This function divides our collection into small chunks of the size provided as the second argument, as shown below.

res = Enum.chunk([1, 2, 3, 4, 5, 6], 2)
IO.puts(res)

The output is:

[[1, 2], [3, 4], [5, 6]]

 

each

At times we may want to iterate over a collection without producing a new value, this is why the use of each function is necessary.

Enum.each(["Hello", "Every", "one"], fn(s) -> IO.puts(s) end)

The output is:

Hello
Every
one

 

map

The map functions is used when we want to apply our function to each item and produce a new collection. The map function is one of the most useful constructs in functional programming as it is quite expressive and short.

Now, let's consider an example to understand this. In this example we will double the values stored in a list and store them in a new list res:

res = Enum.map([2, 5, 3, 6], fn(a) -> a*2 end)
IO.puts(res)

The output is:

[4, 10, 6, 12]

 

reduce

The reduce function simply assists us in reducing our enumerable to a single value. in order to do this, an optional accumulator is supplied ( e.g 5) to be passed into our function; but if no accumulator is provided, the first value is used. This is shown below:

res = Enum.reduce([1, 2, 3, 4], 5, fn(x, accum) -> x + accum end)
IO.puts(res)

The output is:

15

Note that the accumulator is the initial value passed to the fn. You can see that from the second call onwards the value returned from the previous call is passed as accum. 

Also, we can use reduce without the accumulator, as shown below:

res = Enum.reduce([1, 2, 3, 4], fn(x, accum) -> x + accum end)
IO.puts(res)

The output is:

10

 

uniq

This function removes duplicates from our collection and returns only the set of elements in the collection. Check out the example below:

res = Enum.uniq([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
IO.puts(res)

The output is:

[1, 2, 3, 4]

 

Eager Evaluation

Every function in the Enum module is eager. Most functions expect an enumerable and return a list back, meaning that when performing multiple operations with Enum, as you may know, each operation is going to generate an intermediate list until we reach the result. Check out the example below to understand better.

odd? = &(odd? = &(rem(&1, 2) != 0) 
res = 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum 
IO.puts(res) 

The output is:

7500000000

From the above code, there is a pipeline of operation. Because we start with a range of and then multiply each element in the range by 3. This first operation now creates and returns a list with 100_000 items. After we will keep all odd elements from the list, generating a new list, now with 50_000 items, and then we sum all entries.

The |> symbol used in the above code is the pipe operator, as it simply takes the output from the expression on its left side and passes it as the first argument to the function call on its right side. This is similar to the Unix | operator. The pipe operator’s purpose is to highlight the flow of data being transformed by a series of functions.

Without the pipe operator, the code looks complicated, as shown below.

Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))

Although there are numerous functions, only a few important ones have been described in this tutorial.