Elixir - Enumerables



An enumerable is an object that may be enumerated. "Enumerated" means to count off the members of a set/collection/category one by one (usually in order, usually by name).

Elixir provides the concept of enumerables and the Enum module to work with them. The functions in the Enum module are limited to, as the name says, enumerating values in data structures. Example of an enumerable data structure is a list, tuple, map, etc. The Enum module provides us with a little over 100 functions to deal with enums. We will discuss a few important functions in this chapter.

All of these functions take an enumerable as the first element and a function as the second and work on them. The functions are described below.

all?

When we use all? function, the entire collection must evaluate to true otherwise false will be returned. For example, to check if all of the elements in the list are odd numbers, then.

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

When the above program is run, it produces the following result −

false

This is because not all elements of this list are odd.

any?

As the name suggests, this function returns true if any element of the collection evaluates to true. For example −

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

When the above program is run, it produces the following result −

true

chunk

This function divides our collection into small chunks of the size provided as the second argument. For example −

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

When the above program is run, it produces the following result −

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

each

It may be necessary to iterate over a collection without producing a new value, for this case we use the each function −

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

When the above program is run, it produces the following result −

Hello
Every
one

map

To apply our function to each item and produce a new collection we use the map function. It is one of the most useful constructs in functional programming as it is quite expressive and short. Let us consider an example to understand this. We will double the values stored in a list and store it in a new list res

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

When the above program is run, it produces the following result −

[4, 10, 6, 12]

reduce

The reduce function helps us reduce our enumerable to a single value. To do this, we supply an optional accumulator (5 in this example) to be passed into our function; if no accumulator is provided, the first value is used −

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

When the above program is run, it produces the following result −

15

The accumulator is the initial value passed to the fn. From the second call onwards the value returned from previous call is passed as accum. We can also use reduce without the accumulator −

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

When the above program is run, it produces the following result −

10

uniq

The uniq function removes duplicates from our collection and returns only the set of elements in the collection. For example −

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

When running above program, it produces the following result −

[1, 2, 3, 4]

Eager Evaluation

All the functions in the Enum module are eager. Many functions expect an enumerable and return a list back. This means that when performing multiple operations with Enum, each operation is going to generate an intermediate list until we reach the result. Let us consider the following example to understand this −

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

When the above program is run, it produces the following result −

7500000000

The example above has a pipeline of operations. We start with a range and then multiply each element in the range by 3. This first operation will now create and return a list with 100_000 items. Then we 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 snippet above is the pipe operator: 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. It’s similar to the Unix | operator. Its purpose is to highlight the flow of data being transformed by a series of functions.

Without the pipe operator, the code looks complicated −

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

We have many other functions, however, only a few important ones have been described here.

Advertisements