For Loops For Erlang Beginners
Filed under: Erlang, Programming, Uncategorized | 2 Comments »
Newcomers to Erlang are often dismayed to learn that it does not provide syntax for any of the looping constructs typical to procedural languages. This means no while, no for, and no foreach*.
In this article I’ll show you how to write a function in Erlang that emulates the functionality of the for loop that is familiar to most programmers. Using it, you will be able to have an arbitrary code path be executed a specified number of times.
What We’re Up Against
In a procedural language like C we can write something like this
for(int i = 0; i < 10; i++)
{
cout << "Line " << i << endl;
}
Since this tutorial is aimed at beginners, I’ll go over the basics of Erlang functions before diving into how to emulate the above code.
Functions, Pattern Matching, and Guard Sequences
In Erlang, you may specify multiple patterns against which calls to a function will be matched. This is similar to function overloading in procedural languages. One important thing to note is that Erlang functions are identified by the combination of their name and their arity. Arity refers to the number of parameters that a function accepts. If we define two functions with the same name but different arity, Erlang will consider them to be two separate functions. Here’s an example of a function of arity 1 that has four different patterns.
example(N) when is_list(N) ->
io:fwrite("A list! ~n")
;
example(N) when N < 5 ->
io:fwrite("An integer less than 5!")
;
example(N) when N > 5 ->
io:fwrite("An integer less than 5!")
;
example({A, B, C}) when is_integer(A) ->
io:fwrite("An tuple with three elements, the first of which is an integer!")
.
When calling a function, the Erlang runtime will attempt to match how the function was called with one of the available patterns. It does this comparison sequentially in the order that the patterns are defined. Two things make up the pattern: the structure of the arguments themselves and the guard sequence. The first three versions use guard sequences to define their pattern while the last example uses both a guard and the structure of the argument itself. Here are four calls that will match the patterns of the above function
%Passing a list
example([1, 2, 3, 4, 5]).
%Passing an integer less than 5 and greater than 5
example(3).
example(6).
%Passing a tuple with three elements
example({1, hello, world}).
Any of the following calls would cause the runtime to throw an error since the calls wouldn’t match any of the patterns specified for the function.
%An integer equal to 5
example(5)
%A tuple with four elements
example({1, 2, 3, 4})
%A tuple with three elements whose first element is not an integer
example({hi, there, world})
Writing The For Function
Here’s how we envision a user calling our code to duplicate the C example from above.
util:for(10, fun(N)-> io:fwrite("Line ~B~n", [N]) end).
util:for(10, fun myModule:printLine/1).
The first line uses Erlang’s anonymous function syntax to specify the code that will be the body of the for loop. The second line shows how to use the fun [module]:[function]/[arity] syntax to pass in a function that has been defined elsewhere.
Here’s how we implement util:for
module.erl
-module(util).
%external functions
-export([for/2]).
%
% for-loop emulation
%
% N: Number of times Fun should be called
% Fun: The high-level function to be called.
% This function is expected to accept a single integer (the loop count)
%
for(N, Fun) when is_integer(N), is_function(Fun, 1) ->
util:for({N, 0}, Fun)
;
for({N, _}, _) when is_integer(N), N < 1 ->
ok
;
for({N, LoopCount}, Fun) when is_integer(N), is_function(Fun, 1) ->
Fun(LoopCount),
util:for({N-1, LoopCount+1}, Fun)
.
First we name our file util.erl since we’ll be placing our function in a module called util. In Erlang, every file defines a module and the name of the file and the module must agree.
Next we declare that we are exporting a function named for of arity 2. The -export keyword makes the function externally visible so that it can be called from other modules. If we did not export the function, it could only be used from within the util module.
Finally, we actually implement the function.
Our function accepts two arguments: an integer and a function that accepts one parameter. These requirements are enforced using guard sequences. For a full list of valid guard sequences, check out section 6.24 of the Erlang Reference Manual.
When the first, “base” pattern of the function is matched, it hands off control to one of the other two patterns.
The second pattern makes use of the _ expression. This is like a wild card. In this pattern, we don’t care about the loop count or the function. This is our “backstop” case that terminates the recursion and it is matched when N is less than 1. It’s important that we define this pattern before the one below it, since they both have the same argument pattern. Remember that Erlang tries to match the patterns sequentially. If the third pattern came before the second one, it would always match and our recursion would never end.
The third pattern calls Fun and passes it the loop count. It then recursively calls itself, decrementing the number of times the function should be called and incrementing the loop count. After reading the last sentence, observant readers might wonder about the fate of the stack. What happens if we call our function and specify an incredibly large number of iterations? Won’t we eventually eat up all the available memory and overflow the stack? Luckily for us, the answer is no! The Erlang compiler is very smart and will be able to optimize our function since it is tail recursive. Tail recursion is when the last instruction of a recursive function is the recursive call. In situations like this, the compiler recognizes that nothing on the stack frame will ever be needed again and so it frees that memory for garbage collection.
That’s all there is to it!
Just Kidding, There’s More
Not bad for a first shot, but the C example is still more powerful since it can be configured with different start, end, and step criteria. In a follow up article I’ll show you how to expand util:for to support these features. But why wait? Everything you need to know has already been revealed!
Until next time, happy coding!
* Erlang does contain a foreach-like function in the lists module, but it’s only for iterating over a list of items and applying a function to each one.