I've previously discussed that Elixir does not have loops and I've showed you some ways of achieving things without loops, the Enum module being the most prominent. However, I've never gone into much detail on the subject of how to get things done without loops.

It wasn't until I was implementing the equivalent of a while loop in one of my recent example projects, and I had do put some thought into how to write the code, did I consider that it is probably a topic worthy of discussion so that you don't get stuck trying to figure out how to accomplish something that would be done with a loop in an imperative languages. So today I'm going to discuss this so that you have some patterns to use for your own code.

For Loops

In imperative languages, there are typically two uses of for loops. One is iterating over every element in a collection (some languages also have foreach loops to make this easier), and the other is changing the value of a counter to use in some calculation or operation. Sometimes the two are combined. Let's look at how to do these things in Elixir.

When iterating over the elements in a collection, you are either typically transforming the elements and creating a transformed collection (often known as mapping) or computing some sort of value based on the elements (known as reducing). In Elixir, this can be accomplished using the functions in the Enum module, which we've covered in detail in previous sections.

Data Transformation For Loops

Here is an example of using a simple for loop in Javascript to transform data.

let numbers = [1, 2, 3, 4, 5];
let transformedNumbers = [];

for(let i = 0; i < numbers.length; i++)
{
	transformedNumbers.push(numbers[i] * 2);
}

//The result is [2, 4, 6, 8, 10]

Here's the same example using a for-of loop in Javascript.

let numbers = [1, 2, 3, 4, 5];
let transformedNumbers = [];

for(let number of numbers)
{
	transformedNumbers.push(number * 2);
}

//The result is [2, 4, 6, 8, 10]

Here's the same example in C# using a simple for loop

List<int> numbers = new List<int>(){1, 2, 3, 4, 5};
List<int> transformedNumbers = new List<int>();

for(int i = 0; i < numbers.Count; i++)
{
	transformedNumbers.Add(numbers[i] * 2);
}

//The result is [2, 4, 6, 8, 10]

Here's the same example in C# using a foreach loop

List<int> numbers = new List<int>(){1, 2, 3, 4, 5};
List<int> transformedNumbers = new List<int>();

foreach(int number in numbers)
{
	transformedNumbers.Add(number * 2);
}

//The result is [2, 4, 6, 8, 10]

Finally, here's the equivalent in Elixir

numbers = [1, 2, 3, 4, 5]

transformed_numbers = Enum.map(numbers, &(&1 * 2))

//The result is [2, 4, 6, 8, 10]

The functions in the Enum module completely replace any data transformation loops.

Counter For Loops

Sometimes you create a for loop to keep track of a counter variable and stop looping when the counter value has reached a particular value.

Here's an example in Javascript.

for(let i = 1; i <= 100; i++)
{
	doSomething(i);
}

In the above example, we call the doSomething function 100 times, passing in the current counter value every time.

Here's the same thing in C#.

for(int i = 1; i < 100; i++)
{
	DoSomething(i);
}

In Elixir, we can accomplish the same thing using an Enum module function and a range. As the function iterates over the values in the range, we get counter-like behavior. For the purposes of this example, I'm going to assume that do_something/1 creates a side effect and use Enum.each/2.

Enum.each(1..100, fn number -> do_something(number) end)

Counter and Data Transformation For Loops

Sometimes we may want to combine both, where we do data transformation and use the value of a counter. Here's an example of such an operation using Javascript.

let numbers = [12, 3, 7, -2, 4];
let transformedNumbers = [];

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

//The result is [0, 3, 14, -6, 16]

In this example, each element in the initial array is multiplied by the index number to create a transformed array.

Here's the same thing in C#.

List<int> numbers = new List<int>(){12, 3, 7, -2, 4};
List<int> transformedNumbers = new List<int>();

for(int i = 0; i < numbers.Count; i++)
{
	transformedNumbers.Add(numbers[i] * i);
}

//The result is [0, 3, 14, -6, 16]

In Elixir, we can accomplish the same thing by first applying Enum.with_index/2, which produces a list of tuples, where the first item is the index and the second item is the data element. We can then feed that to Enum.map/2, which will do the transformation.

numbers = [12, 3, 7, -2, 4]

transformed_numbers = numbers
	|> Enum.with_index()
	|> Enum.map(fn {index, number} -> number * index end)

//The result is [0, 3, 14, -6, 16]

For Loop Replacement

For anyone used to functional equivalents in other languages such as LINQ for C# and lodash for Javascript, this won't seem so strange. I've been writing code in both of those languages that replaces for loops with function calls for a while now, so this wasn't so weird to me. If you've always used loops in the past, then the functional way of doing things may take some more time to get used to.

While Loops

While the functional equivalent of for loops might not that strange for anyone who has used similar methods in imperative languages, the functional equivalent of a while loop will seem a lot weirder.

To do the equivalent of a while loop in Elixir, you call a function recursively until a condition is fulfilled. This sounded strange and wrong to me at first, coming from a background in imperative languages where that would grow the call stack in an uncontrolled manner. However, in Elixir if you make the recursive call the last thing a function does, tail call optimization occurs, the call stack does not grow, and the compiled code will have the same efficiency as a loop.

Here's an example of a while loop that keeps looping, getting a selection, until a certain selection has been selected and the loop exits.

let selection = null;

while (selection !== 3) 
{ 
	selection = getSelection();
	
	doSomething(selection);
}

Here's the C# equivalent.

int selection = null;

while (selection != 3) 
{ 
	selection = GetSelection();
	
	DoSomething(selection);
}

Here's how we would implement the same thing in Elixir.

def process_selection(prev_selection) when selection == 3

end

def process_selection(prev_selection \\ nil)
	selection = get_selection()
	
	do_something(selection)
	
	process_selection(selection)
end

The process_selection/1 function is a recursive function with a base case and a recursive case. The first call would presumably be to process_selection() and the second clause would be called with prev_selection set to nil or whatever the initial value was set to. The process_selection/1 function would then continue to call itself recursively until the selection was 3, in which case the first clause would be called. The first clause does nothing, ending the chain of recursive calls.

That's a while loop, Elixir-style. That would be an inefficient way of doing things in an imperative language, but in Elixir, since the recursive call is the last thing the function does, the recursive calls can be optimized to loop-like efficiency.

Conclusion

So while coding without loops in Elixir may require a change in your way of thinking, it's quite achievable. For me, replacing the while loop with a recursive function call requires more of a mental shift because I simply am not used to writing that sort of code on a regular basis. I have done recursion in imperative languages before, but it's been limited to a few cases where recursion vastly improved the readability of the code and I was certain that the call stack would not grow large. I've never done it just to replace a while loop.