Iterating collections in Elixir

For those new to functional programming and Elixir, something that might come as a surprise is the absence of the for loop. Unlike other programming languages like Ruby, Python, and PHP, there are no for loops in Elixir. Instead, Elixir offers us other ways to iterate over collections.

The first one is using the Enum module from Elixir’s standard library. This module provides functions like each, map, sort, and many others for operations commonly performed on collections.

The other way is using a combination of the cons operator (|), pattern matching, and recursion. This gives developers more control over the iteration process and allows us to write more expressive code. Let’s look at the following code inspired by an example from the Mixing It Up With Elixir course.

The following Conversion.convert_to_euro function takes two arguments: a list with currency rates and an amount in dollars to be converted to euro. Here’s how we call this function to convert $15 USD to euros.

Conversion.convert_to_euro(rates, 15.0)

This function expects the first argument to be a list of maps with different currency values, like this:

[%{"currency" => "peso", "rate" => 20.56},
%{"currency" => "euro", "rate" => 0.94},
%{"currency" => "pound", "rate" => 0.79}]

Let’s dive into the implementation of the convert_to_euro function:

defmodule Conversion do
  def convert_to_euro(rates, amount) do
rate = find_euro(rates)
amount * rate
end
  ...
end

On the very first line, there’s a call to find_euro. The return value from this function is assigned to the rate variable, which is used as a factor to calculate the conversion.

Let’s step into find_euro, and then we’ll go over what it does step by step.

defmodule Conversion do
...
  def find_euro([%{"currency" => "euro", "rate" => rate} | _]) do
rate
end
  def find_euro([_ | tail]) do
find_euro(tail)
end
  def find_euro([]) do
raise "Euro rate not found"
end
end

The find_euro function needs to loop through all the maps from the list until it finds the one with the “currency” key set to “euro”. This function is an example of a recursive function, and it has three clauses.

The first clause is the success case or terminating scenario.

def find_euro([%{"currency" => "euro", "rate" => rate} | _]) do
rate
end

This clause splits the list into head and tail using the cons (|) operator. It finds a successful match when the first element from the list (the head) is a map with the “currency” key set to “euro”. When this happens, we assign the value on the “rate” key to the rate variable and use that as the return value from the function. Since the first element from the list had the value we are looking for, we can explicitly ignore the remaining elements from the list (the tail) by assigning them to the underscore (_) character.

If the first clause does not match and there are still maps remaining on the list, then we go into the second clause.

def find_euro([_ | tail]) do
find_euro(tail)
end

In this clause, we use the cons operator again to split the list into head and tail — but notice we ignore the head element entirely by assigning it to underscore. We know for a fact that the first element doesn’t have a “currency” key set to “euro” — otherwise, it would have matched on the first clause and never reached this one. We can simply avoid assigning it to a variable altogether. The remaining elements from the list are assigned to the tail variable, which is used to call find_euro again. This is where the recursion happens.

Me when I first learned recursion.

This function will keep calling itself and matching on the second clause until it either finds a successful match (first clause) or until it reaches the end of the list (third clause). If that happens, then it will match on the third clause.

def find_euro([]) do
raise "Euro rate not found"
end

We don’t expect to ever get a list of rates where euro is not present, so we’ll consider it a critical situation and use the built-in raise function.

As we can see, not having a for loop doesn’t make Elixir an inferior language. Instead, it makes us rethink the way we approach common programming techniques like iterating over a collection to search for a specific element. In some cases, it might even produce code that’s easier to read and to maintain.

Code School — Try Elixir Course

Code School worked with the Elixir team to create a free introductory Elixir course called “Try Elixir.” Play it now to learn the basics of Elixir and functional programming.