Sequential Erlang

The basic constructs of the Erlang language.

Datatypes

Numerical Types

Integers

  • Large Integers are converted to bignums

If you need to do calculations on integers using a base other than 10, you can use Base#Value notation. (Laurent 2017, 7)

Floats

  • When you divide two integers with /, the result is automatically converted to a floating-point number.

Mathematical Operations

Operation Description Types
+ Unary + Integer or Float
- Unary - Integer or Float
+ Addition Integer or Float
- Subtraction Integer or Float
* Multiplication Integer or Float
/ Floating Point Division Integer or Float
div Integer Division Integer
rem Modulus Operation (Remainder) Integer

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.
  • Access is normally done by position.
  • 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. (Armstrong 2013, 35)

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].
  • Access is normally done by parttern matching.
  • One can add or iterate over lists with the cons operator |, which breaks a list into H|T (head and tail),
  List = [ Element | List ] OR []

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> [ math:pow(X,2) || X <- L, X > 0, X rem 2 == 0 ]

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.

  • Generators are written as Pattern <- ListExpr where ListExpr must be an expression that evaluates to a list of terms.
  • Bitstring generators are written as BitStringPattern <= BitStringExpr where BitStringExpr must be an expression that evaluates to a bitstring.
  • Filters are either predicates or boolean expressions. (Armstrong 2013, 61)

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

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. (Armstrong 2013)

  -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"}

Record BIFs

  • record_info/2
  • is_record/1

Maps

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'}

Binaries and Bitstrings)

A binary is a sequence of unsigned 8-bit bytes, used for storing and processing chunks of data (often data that comes from a file or has been received over a network protocol). (Logan, Merritt, and Carlsson 2010, 30)

A bitstring is written as << Segment1, ..., SegmentN >>, with zero or more segment specifiers between the double less-than/greater-than delimiters. The total length of the bitstring, in bits, is exactly the sum of the lengths of the segments.

A segment specifier can be on one of the following forms:

  • Data
  • Data:Size
  • Data/TypeSpecifiers
  • Data:Size/TypeSpecifiers

(Logan, Merritt, and Carlsson 2010, 67)

A possible set of TypeSpecifiers is the following:

  • integer, float, binary, bytes, bitstring, bits, utf8, utf16, utf32 as base types.
  • Extra qualifiers like signed, unsigned or big, little, native can also be used and need to be separated by hyphens (i.e. integer-unsigned-big).

Bitstring comprehensions

Similar to Lists Comprehensions, we can do bitstring comprehension:

Bitwise operations

Operator Description
band/1 Bitwise and
bor/1 Bitwise or
bxor/1 Bitwise xor
bnot/1 Bitwise negation
bsl/2 Bitshift Left
bsr/2 Bitshoft right

Identifiers

Pids

Every process has a unique identifier, usually referred to as a pid. Pids are a special data type in Erlang and should be thought of as opaque objects. (Logan, Merritt, and Carlsson 2010, 35)

Ports

A port is much like a process, except that it can also communicate with the world outside Erlang (…). Hence, port identifiers are closely related to pids, and the shell prints them on the form #Port<0.472>. (Logan, Merritt, and Carlsson 2010, 36)

For more details about Ports, check the Erlang Interface notes.

References

References in Erlang are used to guarantee the identity of messages, monitors, and other data types or requests. A reference can be generated indirectly by setting up a monitor, but also directly by calling the BIF make_ref/0. References are, for all intents and purposes, unique across a multinode Erlang system. (Cesarini and Vinoski 2016, 38)

Macros

Erlang has a macro facility, implemented by the Erlang preprocessor (epp), which is invoked prior to compilation of source code into BEAM code.

  %% Constants
  -define(ANSWER,42).
  -define(DOUBLE,2*).

  %% Or more complex patterns
  -define(TWICE(F,X),F(F(X))).

Conditional Macros

  • -undef(Flag)
  • -ifdef(Flag)
  • -ifndef(Flag)
  • -else(Flag)
  • -endif

Include Files

Term Comparisson

Using the exactly equal and not exactly equal operators will provide the compiler and type tools with more information and result in more efficient code. (Cesarini and Thompson 2009, 28)

Operator Description
== Equal to
/= Not equal to
=:= Exactly equal to
=/= Exactly not equal to
=< Less than or equal to
>= Greater than or equal to
< Less than
> Greater than

Variables

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. (Armstrong 2013)

  • 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 (=).

Pattern Matching

Pattern matching is used for:

  • Assigning values to variables
  • Redirecting execution flows

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

Functions

Built-in Functions

  • Conventionally, these are part of the erlang module.
  • Mostly written in C for fast execution.

Useful BIFs

  • date/0
  • now/0 and now/1
  • time/0

List BIFs

  • hd/1
  • tl/1
  • length(List)

Tuple BIFs

  • setelement/3
  • size/1
  • erlang:append_element/2

Type conversion BIFs

  • atom_to_list/1 and list_to_atom/1
  • list_to_tuple/1 and tuple_to_list/1
  • float/1 and list_to_float/1
  • float_to_list/1 and integer_to_list/1
  • round/1, trunc/1 and list_to_integer/1

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)].

Recursion

Tail Recursion

Guards

A guard consists of a when keyword followed by a guard expression. The clause will be selected only if the pattern matches and the guard expression evaluate to the atom true. (Cesarini and Thompson 2009, 50)

  %% G1, G2 can be parsed as G1 v G2 and the ';' can be read as an '^', so
  %% it is possible to write very complex guards such as:
  %%    (G1 v G2) ^ (G3 v ... v GK)
  f(Arg1, Arg2, ..., ArgN) when G1(...), G2(...); G3(...), ..., GK(...) ->
    Expr.

Modules

Erlang definitions are contained in modules, which are stored in files of the same name, but with a .erl extension. (…). Erlang programs can be evaluated in the Erlang Shell, invoked by the command erl in your Unix shell. (Cesarini and Vinoski 2016, 22)

Defining Modules

    -module(drop).
    -export([fall_velocity/1, mps_to_mph/1, mps_to_kph/1]).

    fall_velocity(Distance) -> math:sqrt(2 * 9.8 * Distance).
    mps_to_mph(Mps) -> 2.23693629 * Mps.
    mps_to_kph(Mps) -> 3.6 * Mps.

Upgrading Modules

One of the advantages of dynamic typing is the ability to upgrade your code during runtime, without the need to take down the system. One second, you are running a buggy version of a module, but you can load a fix without terminating the process and it starts running the fixed version, retaining its state and variables. (…) At any one time, two versions of a module may exist in the virtual machine: the old and current versions.

(Cesarini and Vinoski 2016, 43)

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
Used to terminate the current process.
throw
Its usage is discouraged as it makes understanding program flow harder to debug.
error
The most common class of errors, can be raised by calling the erlang:error(Term) BIF.

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.

References:

Armstrong, Joe. 2013. “Programming Erlang: Software for a Concurrent World.”
Cesarini, Francesco, and Simon Thompson. 2009. Erlang Programming: A Concurrent Approach to Software Development. O’Reilly Media, Inc.
Cesarini, Francesco, and Steve Vinoski. 2016. Designing for Scalability with Erlang/Otp: Implement Robust, Fault-Tolerant Systems. O’Reilly Media, Inc.
Laurent, Simon St. 2017. Introducing Erlang: Getting Started in Functional Programming. O’Reilly Media, Inc.
Logan, Martin, Eric Merritt, and Richard Carlsson. 2010. Erlang and Otp in Action. Manning Publications Co.

Backlinks: