Does Elixir Have for Loops?

Table of contents:

Since the dawn of civilization, humanity has yearned for one thing — the ability to effortlessly repeat an action until an arbitrary condition has been satisfied.

The for loop is a programming concept that is infinitely versatile. While it’s not always the best choice in every situation, a case can be made for it in virtually any situation where a programmer needs something to occur more than once.

A new developer may be surprised, then, to Google “does elixir have for loops” and be met with the answer “Elixir doesn’t have looping constructs like a for loop or a while loop” (source).

How would you write code without loops?

While recursion in many programming languages comes with the burden on high memory usage, elixir uses tail call optimization to achieve similar performance of traditional looping constructs.

Despite it being fairly simple to write your own implementation of various loops using recursion, you often won’t need to. The Enum and Stream modules provide most of the functionality that you would typically want from a loop.

The main difference between these modules is that they are eager-loaded and lazy-loaded respectively, making Stream more well suited for very large collections. The Stream module will not be explored in depth in this post.

Here’s a few examples of common operations that would replace some code where you might use a for loop:

Map

Map applies a function to every element in a list and returns the results in a list.

numbers = [1, 2, 3, 4]
squares = Enum.map(numbers, fn x -> x * x end)

Filter

Filter returns only the elements that result in true being returned from a given function.

numbers = [1, 2, 3, 4]
odds = Enum.filter(list, fn x -> rem(x, 2) == 1 end)

Find

Find returns the first element in a list that results in true being returned when passed into a given function.

numbers = [1, 2, 3, 4]
first_even = Enum.find(list, fn x -> rem(x, 2) == 0 end)

I’ll be making a post soon with more in depth explanations of different array operations, so make sure to follow me on Dev or subscribe to my newsletter so you don’t miss it. For now though, see the documentation for more details and other available functions.

The above examples show situations where you might replace a for loop with a more specific implementation, but Elixir also has a construct that’s even closer to a for loop…

Loop comprehensions

These are a bit of syntactic sugar that let you do things like map or filter on an enumerable while treating it like a loop.

To square a list of number in JavaScript you might write something like this (I know there’s functions that would simplify this, but this example is here to show how you might do this with a for loop):

let list = [1, 2, 3, 4, 5];
let squares = [];

for (let i = 0; i < list.length; i++) {
	squares.push(list[i] * list[i]);
}

In Elixir, here’s how that same logic would look as a comprehension:

list = [1, 2, 3, 4, 5]
squares = for n <- list, do: n * n

These comprehensions also have an option to pass in a filter. If you wanted to do the same thing as above but only square the odd numbers, it would look something like this:

list = [1, 2, 3, 4, 5]
squares_of_odds = for n <- list, rem(n, 2) == 1, do: n * n

This construct should look familiar to Python developers, who would write the same logic like this:

list = [1, 2, 3, 4, 5]
squares = [x * x for x in list]

# you could also use the power operator and write it like this
squares = [x**2 for x in list]

# if you only wanted to square the odd numbers
squares_of_odds = [x**2 for x in [1, 2, 3, 4] if x % 2 == 1]

Additional options

To add a bit more flexibility, Elixir loop comprehensions include the :into and :reduce options, which you can learn about here.

So, Elixir has for loops?

Not really. Although loop comprehensions look like loops, they’re actually macros that rewrite the code into recursive implementations. Additionally, loop comprehensions take enumerable collections as an argument and produce an enumerable collection as a result. Loops, on the other hand, will continue to execute as long as any arbitrary condition is true.

Further reading

Share this post: