Introduction to Erlang : Declaring Functions
- Introduction to Erlang post series
- Introduction to Erlang : Installing Erlang
- Introduction to Erlang : Typing
- Introduction to Erlang : Basic Types (1/2)
- Introduction to Erlang : Basic Types (2/2)
- Introduction to Erlang : Modules & Compilation
- Introduction to Erlang : Declaring Functions
- Introduction to Erlang : Control Flow
- Introduction to Erlang : Recursion (1/2)
- Introduciton to Erlang : Recursion (2/2)
- Introduction to Erlang : BIFs & Predefined Modules
- Introduction to Erlang : List & lists Module
- Introduction to Erlang : List Comprehension
- Introduction to Erlang : Concurrency (Processes)
- Introduction to Erlang : Message Passing
- Introduction to Erlang : Shared Memory Example
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 andHead
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
andorelse
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.
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
Hi Ben. Thanks for your comment. I’m not planning to take it down, no, so please feel free to use it for your course. (I should update my site a bit though :|)