The Elixir Enum Module

avatar
(Edited)

image.png

Elixir Enum Module

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> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2,4,6]

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> Enum.map(dict, 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.

at/2
filter/2
reduce/3
into/2
take/2

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 and a helpful image to help grasp this concept:

image.png

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

# Shortened with capture operator:
# Parentheses are required around the capture in this
# case to make it clear where the capture starts and ends.
Enum.map([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
Enum.map ["hello\n", "there\n"], fn(word) ->
  String.replace(word, "\n", "")
end

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

Read the documentation on the Capture operator for more details.

Stream

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:

%Stream{
  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
list
|> Enum.filter(&is_number/1)
|> Enum.filter(&(&1 * 2 == 4))
# Iterates over the list once
list
|> Stream.filter(&is_number/1)
|> Stream.filter(&(&1 * 2 == 4))
|> Enum.into([])

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

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

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

Checkout some more Stream building functions in the documentation:
cycle/1
iterate/2
resource/3

Resources

Enum Documentation
Capture Operator Documentation
Stream Documentation

https://nolanm.dev/posts/16-the-elixir-enum-module



0
0
0.000
2 comments
avatar

Nice post! I've never done any research about Elixir so for me this was the first time seeing any code written in it. Good luck on learning the language! I also encourage you to post your programming related things in the Programming Community. We welcome everyone from total beginners to the more advanced developers to share their experiences, projects, etc!

0
0
0.000