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 intoH|T
(head
andtail
),
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, ...]
, 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. (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
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
orbig
,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
, andA_long_name
are all variables. Names beginning with lowercase letters—for example,monday
orfriday
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
andnow/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
andlist_to_atom/1
list_to_tuple/1
andtuple_to_list/1
float/1
andlist_to_float/1
float_to_list/1
andinteger_to_list/1
round/1
,trunc/1
andlist_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 commanderl
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.
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.