The Elixir Enum Module

in StemSocial4 years ago (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

Sort:  

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!