two-argument version) is split into
two clauses, separated by a semicolon. When factorial/2 is called, the
actual parameters are tested against
the patterns in each clause head in
turn to find the first match, then the
body (after the arrow) is evaluated.
The value of the final expression in the
body is the return value of the call; no
explicit return statement is needed.
Erlang is dynamically typed, so a call
to factorial(“pancake”) will compile but will raise a runtime exception
when it fails to match any clause. Tail-calls are optimized, so this code will
run in constant space.
Lists are enclosed in square brackets (see Figure 1B). A single vertical
bar separates the first element from
the rest of the list. If a list is used in
a clause head pattern, it will match
list values, separating them into their
components. A list with a double vertical bar is a “list comprehension,” constructing a list through generator and
filter expressions. A double-plus (++)
concatenates lists.
Tuples (vectors) are enclosed in
curly braces (see Figure 1C). Tuples in
patterns will extract components out
of tuples that they match. Identifiers
that start with an uppercase letter are
variables; those that start in lowercase
are atoms (symbolic constants such as
enum values, but with no need to define
a numerical representation). Boolean
values are represented simply as atoms
true and false. An underscore (_) in
a pattern matches any value and does
not create a binding. If the same fresh
variable occurs several times in a pattern, the occurrences must be equal to
match. Variables in Erlang are
single-assignment (once a variable is bound to
a value, that value never changes).
Not all list-processing operations
can be expressed in list comprehensions. When we do need to write list-processing code directly, a common
idiom is to provide one clause for handling the empty list and another for
processing the first element of a nonempty list. The foldl/3 function shown
in Figure 1D is a common utility that
chains a two-argument function across
a list, seeded by an initial value. Erlang
allows anonymous functions (“fun’s”
or closures) to be defined on the fly,
passed as arguments, or returned from
functions.
figure 1: example1.erl.
a
-module(example1).
-export([factorial/1, qsort/1, member/2, foldl/3, sum/1]).
Compute the factorial of a positive integer.
factorial(N) when is_integer(N), N > 0 -> factorial(N, 1).
A helper function which maintains an accumulator.
factorial( 1, Acc) -> Acc;
factorial(N, Acc) when N > 1 -> factorial(N - 1, N Acc).
B
Return a sorted copy of a list.
qsort([]) -> [];
qsort([Pivot | Xs]) ->
qsort([X || X <- Xs, X < Pivot])
++ [Pivot]
++ qsort([X || X <- Xs, X >= Pivot]).
c
Is X an element of a binary search tree?
member(_, empty) -> false;
member(X, {_, X, _}) -> true;
member(X, {Left, Y, _}) when X < Y -> member(X, Left);
member(X, {_, _, Right}) -> member(X, Right).
D
“Fold” a function across elements of a list, seeding
% with an initial value.
e.g. foldl(F, A0, [A, B, C]) = F(C, F(B, F(A, A0)))
foldl(_, Acc, []) -> Acc;
foldl(F, Acc, [X | Xs]) ->
NewAcc = F(X, Acc),
foldl(F, NewAcc, Xs).
Give the sum of a list of numbers.
sum(Numbers) -> foldl(fun(N, Total) -> N + Total end, 0, Numbers).
Erlang has expressions that look
like assignments but have a different semantics. The right-hand side
of = is evaluated and then matched
against the pattern on the left-hand
side, just as when selecting a clause
to match a function call. A new variable in a pattern will match against
the corresponding value from the
right-hand side.
}
// Re-initialize counter to zero.
public synchronized void
reset() {
nextVal = 0;
}
}
concurrent erlang
Let’s introduce concurrent Erlang by
translating a small example from Java:
Sequence.java
// A shared counter.
public class Sequence {
private int nextVal = 0;
// Retrieve counter and
// increment.
public synchronized int
getNext() {
return nextVal++;
A sequence is created as an object
on the heap, potentially accessible by
multiple threads. The synchronized
keyword means that all threads calling the method must first take a lock
on the object. Under the protection of
the lock, the shared state is read and
updated, returning the pre-increment
value. Without this synchronization,
two threads could obtain the same value from getNext(), or the effects of a
reset() could be ignored.
Let’s start with a “raw” approach to
Erlang, using the concurrency primitives directly.