The Elixir Enum Module

The Elixir Enum Module

By Nolan Mayersky | Nolan's Rambles | 19 Sep 2020

To help myself in learning more Elixir, I'm going to continue these Elixir guide/tutorial posts where I try to explain a different piece of Elixir in each post. Today's post will be covering the Enum module and it's usage.

The Enum Module provides a set of algorithms that enumerate over collections according to the Enumerable protocol:

iex>[1, 2, 3], fn(x) -> x * 2 end)

Some particular types, like dictionaries, yield a specific format on enumeration. For dicts, the argument is always a {key, value} tuple:

iex> dict = %{a: 1, b: 2}
iex>, fn {k, v} -> {k, v * 2} end)
[a: 2, b: 4]

Below are some other functions available in the Enum Module along with a link to it's definition in the Enum Module documentation.


The Capture Operator

Let’s first talk about capturing function. Capture means "&" can turn a function into an anonymous function which can be passed as arguments to other function or be bound to a variable.

& can capture two types of functions, a function with given name and arity from a module.

The notation is: &(module_name.function_name/arity) ex:

speak = &(IO.puts/1)
speak.("hello")  # hello

We capture puts function from IO module and bind it with a local name speak.

The capture operator can be a little difficult to wrap your head around, so here are some examples.

# Multiple each number by itself [1, 2, 3], fn(num) ->
  num * num

# Shortened with capture operator:
# Parentheses are required around the capture in this
# case to make it clear where the capture starts and ends.[1, 2, 3], &(&1 * &1))

When you are capturing a named function, you don’t need the parentheses:

# Remove \n chars from the end of each word ["hello\n", "there\n"], fn(word) ->
  String.replace(word, "\n", "")

# Shortened with capture operator:["hello\n", "there\n"], 
         &String.replace(&1, "\n", ""))

Read the documentation on the Capture operator for more details.


Stream is a lazy version of the Enumerable module. Note that the functions in the Enum module are eager: they always start the enumeration of the given collection. The Stream module allows lazy enumeration of collections and provides infinite streams. It implements most Enum functions, but instead of returning a modified list, it returns a struct like this:

  enum: [...], # Enumerable to iterate through
  funs: [...]  # Anonymous functions to run

Since the majority of the functions in Enum enumerate the whole collection and return a list as result, infinite streams need to be carefully used with such functions, as they can potentially run forever. For example:

Enum.each Stream.cycle([1,2,3]), &IO.puts(&1)

Streams are lazy, and only iterate over the list once:

# Iterates over the list twice
|> Enum.filter(&is_number/1)
|> Enum.filter(&(&1 * 2 == 4))
# Iterates over the list once
|> Stream.filter(&is_number/1)
|> Stream.filter(&(&1 * 2 == 4))
|> Enum.into([])

Use Enum.into/2 or to make a stream do work.

|> Stream.filter(&is_number/1)
|> Stream.filter(&(&1 * 2 == 4))
|> Enum.into([])

[1, 2, 3]
|> Stream.each(&IO.puts/1)

Checkout some more Stream building functions in the documentation:


Enum Documentation
Capture Operator Documentation
Stream Documentation


Nolan Mayersky
Nolan Mayersky

I'm a 30 year old software engineer from the good ol' USA.

Nolan's Rambles
Nolan's Rambles

A blog of my ramblings about software engineering.

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.