Programming Erlang: Software for a Concurrent World by Marcos Benevides
:ID: 1daea4ea-40bc-406d-8d03-906c7f9ec343
Part I: Why Erlang?
Introducing Concurrency
Modeling Concurrency
spawn
is an Erlang primitive that creates a concurrent process and returns a
process identifier (PID) that can used to interact with the newly created process:
spawn(ModName, FuncName, [Arg1, Arg2, ..., ArgN])
The syntax Pid ! Msg
means “send the message Msg
to the process Pid
”. And for
Pid
to process a message we need to pattern match:
receive
{From, Message} ->
...
end
Benefits of Concurrency
- Performance
- Scalability
- Fault Tolerance
- Clarity
A Whirlwind Tour of Erlang
Syntax of Variables and Atoms
Note that Erlang variables start with uppercase characters. So,
X
,This
, andA_long_name
are all variables. Names beginning with lowercase letters—for example,monday
orfriday
are not variables but are symbolic constants called atoms.
Compiling and Running “Hello World” in the Shell
-module(hello).
-export([start/0]).
start() ->
io:format("Hello world~n").
Running the hello.erl
program in the erlang shell.
$ erl
Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:24:24] [ds:24:24:10] [async-threads:1] [jit:ns]
Eshell V14.2.5 (press Ctrl+G to abort, type help(). for help)
1> c(hello).
{ok,hello}
2> hello:start().
Hello world
ok
Compiling Outside the Erlang Shell
$ erlc hello.erl
$ erl -noshell -s hello start -s init stop
erlc
evokes the Erlang compiler from the command line. The compiler compiles the code inhello.erl
and produces an object code file calledhello.beam
.The
erl -noshell
command loads the modulehello
and evaluates the functionhello:start()
. After this, it evaluates the expressioninit:stop()
, which terminates the Erlang session.
Part II: Sequential Programming
Basic Concepts
Variables
- Erlang Variables Do Not Vary
- The scope of a variable is the lexical unit in which it is defined.
- Variables acquire values as the result of a successful pattern matching
operation (
=
).
Floating-Point Numbers
- When you divide two integers with
/
, the result is automatically converted to a floating-point number. - Integer division is handled by
div
andrem
.
Atoms
- In Erlang, atoms are used to represent constant values.
- Atoms are also global, and this is achieved without the use of macro definitions or include files.
Tuples
- Represents a collection of elements (of any type) that are grouped together.
- A tuple whose first element is an atom is called a
tagged
tuple, i.e.,{book, "The Aleph"}
.
Tuples are created automatically when we declare them and are destroyed when they can no longer be used. Erlang uses a garbage collector to reclaim all unused memory, so we don’t have to worry about memory allocation.
You can also pattern match tuples by using free variables:
1> Point = {point, 10, 45}.
{point,10,45}
2> {point, X, Y} = Point.
{point,10,45}
3> X.
10
4> Y.
45
Lists
- A list is a compound data type with a variable number of terms:
[Term1,...,TermN]
. - One can add or iterate over lists with the
cons
operator|
, which breaks a list intoH|T
(head
andtail
),
Strings
Strictly speaking, there are no strings in Erlang. To represent a string in Erlang, we can choose between representing the string as a list of integers or as a binary. When a string is represented as a list of integers, each element in the list represents a Unicode codepoint.
To print a unicode string one must use the “t” modifier applied to the “s” control character in a formatting string, it accepts all Unicode codepoints and expect binaries to be in UTF-8:
1> X = "a\x{221e}b".
[97,8734,98]
2> io:format("~ts~n",[X]).
a∞b
ok
Modules and Functions
Modules Are Where We Store Code
-module(geometry).
-export([area/1]).
area({rectangle, Width, Height}) -> Width * Height;
area({circle, Radius}) -> 3.14159 * Radius * Radius;
area({square, Side}) -> Side * Side.
Funs: The Basic Unit of Abstraction
Funs
are function closures. Funs
are created by expressions of the form: fun(...) -> ... end
.
Defining Your Own Control Abstractions
If we want additional control structures, we can make our own. Erlang has no for loop, so let’s make one:
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I)|for(I+1, Max, F)].
List Comprehensions
1> L = [1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
2> [ 2*X || X <- L ].
[2,4,6,8,10,12,14]
3>
The most general form of a list comprehension is an expression of the following form:
[X || Qualifier1, Qualifier2, ...]
, whereX
is an arbitrary expression, and each qualifier is either a generator, a bitstring generator, or a filter.
- Generators are written as
Pattern <- ListExpr
whereListExpr
must be an expression that evaluates to a list of terms.- Bitstring generators are written as
BitStringPattern <= BitStringExpr
whereBitStringExpr
must be an expression that evaluates to a bitstring.- Filters are either predicates or boolean expressions.
case and if Expressions
case Expression of
Pattern1 [when Guard1] -> Expr_seq1;
Pattern2 [when Guard2] -> Expr_seq2;
...
end
if
Guard1 -> Expr_seq1;
Guard2 -> Expr_seq2;
...
end
Records and Maps
Naming Tuple Items with Records
(…) records provide a convenient way for associating a tag with each of the elements in a tuple. This allows us to refer to an element of a tuple by name and not by position. A pre-compiler takes the record definition and replaces it with the appropriate tuple reference.
-record(todo, {status=reminder,who=joe,text}).
to load a record from the the shell, one must use the rr
command:
1> rr("records.hrl").
[todo]
2> #todo{}.
#todo{status = reminder,who = joe,text = undefined}
3> X1 = #todo{status=urgent, text="Fix errata in book"}.
#todo{status = urgent,who = joe,text = "Fix errata in book"}
4> X2 = X1#todo{status=done}.
#todo{status = done,who = joe,text = "Fix errata in book"}
Maps: Associative Key-Value Stores
Maps are associative collections of key-value pairs.
1> TaskPending = #{ status => pending, description => 'feed cats' }.
#{status => pending,description => 'feed cats'}
2> TaskDone = TaskPending#{ status := done }.
#{status => done,description => 'feed cats'}
Error Handling in Sequential Programs
try FuncOrExpressionSeq of
Pattern1 [when Guard1] -> Expressions1;
Pattern2 [when Guard2] -> Expressions2;
...
catch
ExceptionType1: ExPattern1 [when ExGuard1] -> ExExpressions1;
ExceptionType2: ExPattern2 [when ExGuard2] -> ExExpressions2;
...
after
AfterExpressions
end
exit/1
- Used to terminate the current process.
throw
- Used as a documentation to the caller, to show that a function might throw this exception.
- error
- Crashing errors.
Fail Fast and Noisily, Fail Politely
In Erlang, when an error is detected internally by the system or is detected by program logic, the correct approach is to crash immediately and generate a meaningful error message.
(…)
Second, fail politely means that only the programmer should see the detailed error messages produced when a program crashes. A user of the program should never see these messages.