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

A Whirlwind Tour of Erlang

Syntax of Variables and Atoms

Note that Erlang variables start with uppercase characters. So, X, This, and A_long_name are all variables. Names beginning with lowercase letters—for example, monday or friday 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 in hello.erl and produces an object code file called hello.beam.

The erl -noshell command loads the module hello and evaluates the function hello:start(). After this, it evaluates the expression init:stop(), which terminates the Erlang session.

Part II: Sequential Programming

Basic Concepts

Variables

Floating-Point Numbers

Atoms

Tuples

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

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, ...], where X is an arbitrary expression, and each qualifier is either a generator, a bitstring generator, or a filter.

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.

Binaries and the Bit Syntax