Introduction to Erlang : Declaring Functions

Functions

As you know by now, Erlang is a functional programming language. As this suggests, functions are the basic “ingredient” of an Erlang program. In my point of view, different programming paradigms pose different problem solving philosophy:

  • Procedural: describe the steps needed to be taken to solve the problem
  • Object-orientation: design the objects that will lead you to the solution
  • Logical (Declarative): describe the problem properly and let the language solve it
  • Functional: define small and precise functions that alltogether solve the problem

With this in mind, lets continue on how to declare a function (in a module).

Examples

While introducing functions, I will use several examples that implement list functions, although there are built-ins (BIFs) that implement the same functionality. The reason I will do so is that most of these functions are small, easy to understand, and operate on lists; one of the most, if not the most, important type in Erlang.

Declaring a Function

A simple function declaration has the following format:

function_name(Argument1, Argument2, ...) ->
    Statement1,
    Statement2,
    ... .

Where a statement can be another function call, an assignement, a comparison, a control statement (if for example), or a statement called for its side effects.

Pattern Matching

Multiple clauses of the funtion can be defined utilizing pattern matching. For example:

% month/1
month(1) -> jan;
month(2) -> feb;
month(3) -> mar;
month(4) -> apr;
month(5) -> may;
month(6) -> jun;
month(7) -> jul;
month(8) -> aug;
month(9) -> sep;
month(10) -> oct;
month(11) -> nov;
month(12) -> dec;
month(_) -> uknown.
 
% is_empty/1
is_empty([]) ->
    true;
is_empty([_ | _]) ->
    false;
is_empty(_) ->
    {error, not_a_list}.

All the clauses will be pattern match sequentially (so the first clause is tested first, or at list this is the result the user sees) and the first to match will be used. Notice the following:

  • the different clauses, except the last one, finish with a semicolon (;). The semicolon stands for the or in Erlang. The last one, as expected, terminates with a full stop (.)
  • the last clause is a catch all one, because it will pattern match any input. The ‘_’ variable is the don’t care variable that matches any value. The value that will be pattern matched with ‘_’ will not be saved in a variable and you will not be able to retrieve its value once matched.
  • in the is_empty/1 notice the pattern matching for lists. As we saw in the Data Types 2 post, an empty list pattern matches the [], while a non-empty matches the [Head | Tail]. Interpret the second pattern matching as: since in [Head | Tail] Tail should be a list and Head should be a term, the list that pattern matches has to have at least one element, thus cannot be the empty list. For the same reason the pattern [H1, H2 | Tail] matches any list with 2 or more elements.
 1> c(mon).
{ok,mon}
2> mon:month(3).
mar
3> mon:month(31).
uknown
4> c(mlists).
{ok,mlists}
5> mlists:is_empty([]).
true
6> mlists:is_empty([1]).
false
7> mlists:is_empty([1,2,3,4]).
false
8> mlists:is_empty({[1,2,3,4], a}).
{error,not_a_list}

An interesting ability in pattern matching is the following:

dupl_head([]) ->
    [];
dupl_head(List = [Head | _Tail]) ->
    [Head | List].
 
%instead of
dupl_head2([]) ->
    [];
dupl_head2([Head | Tail]) ->
    [Head, Head | Tail].

which duplicates the first element of the list:

2> mlists:dupl_head([1,2,3,4]).
[1,1,2,3,4]

What is interesting is that you can both “break” the list with pattern matching and “keep” the whole list for later use.

Guards

In addition to the pattern matching done on the clause’s arguments, you can use some additional constraints for selecting which clause matches. These constraints are called guards and can also be used with the case and receive construct (we will see them in later posts). A guard is signified by the when keyword:

function(Arg1, Arg1, ...) when guard_expression ->
   function_body;
...

For example:

abs(N) when is_integer(N), N < 0 ->
    -N;
abs(N) when is_integer(N) ->
    N.
 
% run
2> mon:abs(1).
1
3> mon:abs(-1). 
1
4> mon:abs(a). 
** exception error: no function clause matching mon:abs(a)
5> mon:abs(-1.1).
** exception error: no function clause matching mon:abs(-1.1)

Notice that abs/1 does not work with a float as input, because of the is_integer/1 guard. As you can see in this example, several guards can be used by separating the with comma (,).

Guard Expressions

Not every expression can be used as a guard. The following list mentions the allowed constructs:

  • the atom true
  • other constants (terms and bound variables), all regarded as false
  • calls to the BIFs is_atom/1, is_binary/1, is_bitstring/1, is_boolean/1, is_float/1, is_function/1, is_function/2, is_integer/1, is_list/1, is_number/1, is_pid/1, is_port/1, is_record/2, is_record/3, is_reference/1, is_tuple/1 that do type checks
  • term comparisons
  • arithmetic expressions
  • boolean expressions
  • contructs using the andalso and orelse

No user functions are allowed as guards, in order to avoid having side-effects (such as printing) within the guard.

Multiple Guard Expressions

As you already saw, you can use a comma (,) to have more than one guard for a clause. Comma has the same effect as the and keyword. For example:

abs(N) when is_integer(N), N < 0 ->
    -N;
abs(N) when (is_integer(N)) and (N >= 0) ->
    N.

The same is true about the semicolon (;) and the or keyword.

is_number(N) when is_integer(N); is_float(N) ->
    true;
is_number(N) when not(is_integer(N) or is_float(N)) ->
    false.

It should be obvious that the guard in the second clause does not make any sense, since it is already “covered” by the guard in the first clause. As you noticed, you can also use the not keyword for negation.

Finally, the orelse and andelse are shortcut evaluations for the or and and respectively. They evaluate without looking at the right-hand side when possible ((true orelse Whatever) and (false andelse Whatever)).

Next

In the next post, I will introduce the if and case control flow statements.

Series NavigationIntroduction to Erlang : Modules & CompilationIntroduction to Erlang : Control Flow

2 Responses to “Introduction to Erlang : Declaring Functions”

  • Hi,
    This seems like a nice tutorial on Erlang.
    I’m looking into using Erlang for an intro course on parallel and distributed computing, and I might like to offer this to students. Would that be ok, or do you have any intention to take this down?
    Thanks in advance.
    –bksteele

Leave a Reply

*

Posts’ Calendar
March 2011
M T W T F S S
 123456
78910111213
14151617181920
21222324252627
28293031