Appendix 1: Erlang/OTP Cheat Sheets

Cheat Sheets

April 28, 2021

This section contains various reminders to jog your memory if you’re not too fresh on basic Erlang data, types, or syntax.

Data Types

NameDescriptionDialyzerExample Syntax
integernumber without decimalsinteger(), pos_integer(), non_neg_integer()1, 2, 3, -213, 16#01FF, 2#101011
floatnumber with decimalsfloat()1.0, -1.0, 123.12, 1.0e232
numbereither floats or integersnumber()1.0, 1
atomliterals, constants with their own name for valueatom()abc, 'abc', some_atom@erlang, 'atom with spaces'
booleanatoms true or falseboolean()true, false
referenceunique opaque valuereference()make_ref()
funanonymous functionfun(), fun((ArgType) -> RetType)<code>fun(X) -> X end, fun F(0) -> []; F(N) -> [1 | F(N-1)] end</code>
portopaque type for a file descriptorport()N/A
pidprocess identifierpid()<0.213.0>
tuplegroup a known set of elementstuple(), {A, B, C}{celsius, 42}, {a, b, c}, {ok, {X, Y}}
mapa dictionary of termsmap(), #{KType => VType}, #{specific_key := VType}#{a => b, c => d}, Existing#{key := Updated}
nilan empty list[][]
listrecursive structure for a list of termslist(), [Type][a, b, c], <code>[a | [b | [c | []]]]</code>, "a string is a list"
binarya flat byte sequencebinary()<<1,2,3,4>>, <<"a string can be a binary">>, <<X:Size/type, _Rest/binary>>

Term ordering: number < atom < reference < fun < port < pid < tuple < map < nil < list < binary

Modules and Syntax

%%% This is a module-level comment
%%% @doc This tag includes officiel EDoc documentation.
%%% It can be useful for people to consule
%%% @end
%%% Generate documentation with rebar3 edoc

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Let's start with Module Attributes %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% This is an attribute or function-specific comment
%% attributes start with a `-', functions with letters.
%% This file should be saved as `sample.erl'
-module(sample).

%% Functions are described in the form Name/Arity, and must
%% be exported through an `-export([...]).' module attribute
-export([f/0, f/1]).
-export([x/0]).         % multiple export attributes can exist

%% You can "import" functions from another module, but
%% for clarity's sake (and because there's no namespaces)
%% nobody really does that
-import(module, [y/0]).

%% .hrl files contain headers, and are imported directly
%% within the module.
%% The following includes a private header file from src/
%% or a public header file from include/ in the current app
-include("some_file.hrl").
%% The following includes a public header file from the
%% include/ file of another application
-include_lib("appname/include/some_file.hrl").

%% specify an interface you implement:
-behaviour(gen_server).

%% Define a record (a tuple that compilers handles in a
%% special way)
-record(struct, {key = default :: term(),
                 other_key     :: undefined | integer()}).

%% Just C-style macros
-define(VALUE, 42).        % ?VALUE in this module becomes `42'
-define(SQUARE(X), (X*X)). % function macro
-define(DBG(Call),         % a fancy debug macro: ?DBG(2 + 2)
        io:format("DBG: ~s (~p): ~p~n",
                  [??Call, {?MODULE, ?LINE}, Call])).

%% Conditionals
-ifdef(MACRO_NAME).        % opposite: -ifndef(MACRO_NAME).
-define(OTHER_MACRO, ok).
-else.                     % other option: -elif(NAME).
-define(MACRO_NAME, ok).
-endif.

%% Type definitions
-type my_type() :: number() | boolean().
-type my_container(T) :: {[T], [T], my_type(), mod:type()}
-export_type([my_type/0, my_container/1]).

%% you can also define custom attributes:
-my_attribute(hello_there).
-author("Duke Erlington").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% And now modules for code and functions %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @doc A function with 0 arguments returning an atom
-spec f() -> term(). % optional spec
f() -> ok.

-spec f(number()) -> float().
f(N) -> N + 1.0.

%% Pattern matching with clauses
x([]) -> [];  % base recursive clause for a list
x([_H|T] -> [x | T]. % replace list element with `x' atom

%% @private variable binding rules
same_list(X = [_|_], X) -> true;
same_list([], []) -> true;
same_list(_, _) -> false.

%% Operators in the language
operators(X, Y) ->
    +X, -Y, % unary
    X + Y, X - Y, X * Y, X / Y,   % any numbers
    X div Y, X rem Y,             % integers-only
    X band Y, X bor Y, X bxor Y,  % binary operators
    X bsl Y, X bsr L,             % bit shifting
    not X,                        % boolean not
    X andalso Y, X orelse Y,      % shortcircuit boolean operators
    X < Y, X > Y, X >= Y, X =< Y, % comparison
    X == Y, X /= Y,               % equality (float == int)
    X =:= Y, X =/= Y,             % strict equality (float =/= int)
    X ++ Y, X -- Y,               % append Y to X, delete Y from X
    X ! Y.                        % send message Y to process X

%% Using guards. Valid guard expressions at:
%% erlang.org/doc/reference_manual/expressions.html#guard-sequences
comfortable({celsius, X}) when X >= 18, X =< 26 -> % AND clauses
    true;
comfortable({celsius, _}) ->
    false.

uncomfortable({celsius, X}) when X =< 18; X >= 26 -> % OR clauses
    true;
uncomfortable({celsius, _}) ->
    false.

%% difference with 'andalso' and 'orelse'
conds(X) when (is_number(X) orelse is_integer(X))
               andalso X < 9 ->
    %% equivalent (A AND B) OR C
    true;
conds(X) when is_number(X); is_integer(X), X < 9 ->
    %% - parentheses impossible with , or ;
    %% - equivalent to A OR (B AND C)
    true;
conds(T) when element(1, T) == celsius; is_integer(T) ->
    %% element/2 extracts an element from a tuple. If `T' is
    %% not a tuple, the call fails and `is_integer/1' is tried
    %% instead
    true;
conds(T) when element(1, T) == celsius orelse is_integer(T) ->
    %% this can never work: if element/2 fails, the whole
    %% `orlese' expressoin fails and `is_integer/1' is skipped
    true.

%% Conditionals
conditional('if', Light) ->
    if Light == red -> stop;
       Light == green; Light == yellow -> go_fast;
       true -> burnout % else clause!
    end;
conditional('case', {Light, IsLate}) ->
    case Light of
        green -> go;
        yellow when IsLate -> go_fast;
        _ -> stop
    end;
conditional(pattern, green) -> go;
conditional(pattern, yellow) -> slow;
conditional(pattern, red) -> stop.

%% List and binary comprehensions
comp(ListA, ListB) ->
    [X*X || X <- ListA, X rem 2 == 0], % square even numbers
    [{X,Y} || X <- ListA, Y <- ListB], % all possible pairs
    << <<X:8>> || X <- ListA >>.       % turn list into bytes
comp(BinA, BinB) -> % now with binaries
    << <<X*X:32>> || <<X:8>> <= Bin, X rem 2 == 0 >>,
    [{X,Y} || <<X:32>> <= BinA, <<Y:8>> <= BinB],
    [X || <<X:8>> <= BinA].

%% Anonymous and higher order functions
higher_order() ->
    If = fun(Light) -> conditional('if', Light) end,
    Case = fun(Light) -> conditional('case', {Light, true}) end,
    lists:map(If, [green, yellow, red]),
    lists:map(Case, [green, yellow, red]),
    If(red), % can be called literally
    lists:map(fun(X) -> X*X end, [1,2,3,4,5]).

try_catch() ->
    try
        some_call(),     % exceptions in this call are caught as well
        {ok, val},       % common good return value to pattern match
        {error, reason}, % common bad return value to pattern match
        % any of these expression aborts the execution flow
        throw(reason1), % non-local returns, internal exceptions
        error(reason2), % unfixable error
        exit(reason3)   % the process should terminate
    of  % this section is optional: exceptions here are not caught
        {ok, V} ->
            do_something(V),
            try_catch(); % safely recurse without blowing stack
        {error, R} ->
            {error, R} % just return
    catch % this section is optional: various patterns
        throw:reason1 -> handled;
        reason2 -> oops; % never matches, `throw' is implicit type
        error:reason2 -> handled;
        exit:reason3 -> handled;
        throw:_ -> wildcard_throws;
        E:R when is_error(E) -> any_error;
        _:_:S -> {stacktrace, S}; % extract stacktrace
    after -> % this is an optional 'finally' block
        finally
    end.

Processes and Signals

%% Start a new process
Pid = spawn(fun() -> some_loop(Arg) end)
Pid = spawn('[email protected]', fun() -> some_loop(Arg) end)
Pid = spawn(some_module, some_loop, [Arg])
Pid = spawn('[email protected]', some_module, some_loop, [Arg])
%% Spawn a linked process
Pid = spawn_link(...) % 1-4 arguments as with spawn/1-4
%% Spawn a monitored process atomically
{Pid, Ref} = spawn_monitor(fun() -> some_loop(Arg) end)
{Pid, Ref} = spawn_monitor(some_module, some_loop, [Arg])
%% Spawn with fancy options
spawn_opt(Fun, Opts)
spawn_opt(Node, Fun, Opts)
spawn_opt(Mod, Fun, Args, Opts)
spawn_opt(Node, Mod, Fun, Args, Opts)
%% Options must respect the following spec; many are advanced
[link | monitor |
 {priority, low | normal | high | max} |    % don't touch
 {fullsweep_after, integer() >= 0} |        % full GC
 {min_heap_size, Words :: integer() >= 0} | % perf tuning
 {min_bin_heap_size, Words} |
 {max_heap_size,                    % heap size after which
   Words |                          % the process may be killed. Use
   #{size => integer() >= 0,        % to indirectly set max queue sizes
     kill => boolean(),
     error_logger => boolean()}}

%% send an exit signal to a process
exit(Pid, Reason)

%% Receive a message
receive
    Pattern1 when OptionalGuard1 ->
        Expression1;
    Pattern2 when OptionalGuard2 ->
        Expression2
after Milliseconds -> % optional
    Expression
end

%% Naming processes
true = register(atom_name, Pid)
true = unregister(atom_name)
Pid | undefined = whereis(atom_name)

%% Monitor
Ref = erlang:monitor(process, Pid)
true = erlang:demonitor(Ref)
true | false = erlang:demonitor(Ref, [flush | info])

%% Links
link(Pid)
unlink(Pid)
process_info(trap_exit, true | false)

And the semantics for links and monitors, in diagram forms:

Figure 1: Monitors are unidirectional informational signals, and they stack

Figure 1: Monitors are unidirectional informational signals, and they stack

Figure 2: Untrapped links are bidirectional and kill the other process, except if the reason is &rsquo;normal'

Figure 2: Untrapped links are bidirectional and kill the other process, except if the reason is ’normal'

Figure 3: Trapped links are converted to messages, except for the untrappable &lsquo;kill&rsquo; reason

Figure 3: Trapped links are converted to messages, except for the untrappable ‘kill’ reason

OTP processes do have slightly different semantics due to supervision shenanigans:

Figure 4: Untrapped links work the same for OTP

Figure 4: Untrapped links work the same for OTP

Figure 5: Trapped links behave in a special way when the parent of a process is the one that dies

Figure 5: Trapped links behave in a special way when the parent of a process is the one that dies

Figure 6: Supervisors log things differently based on the termination reason

Figure 6: Supervisors log things differently based on the termination reason

Behaviours

Not all OTP behaviours are listed here, only thee most frequently-used ones.

Applications

TriggerCalled ByHandled ByReturnDescription
application:start/1-2client or booting VMstart(Type, Args)<code>{ok, pid()} | {ok, pid(), State}</code>should start the root supervisor
{start_phases, [{Phase, Args}]} in app filekernel booting the appstart_phase(Phase, Type, Args)<code>ok | {error, Reason}</code>Optional. Can isolate specific steps of initialization
application:stop/1app shutting downprop_stop(State)StateOptional. Called before the supervision tree is shut down
application:stop/1app shutting downstop(State)term()called once the app is done running to clean things up
Hot code updateSASL’s release handlerconfig_change(Changed::[{K,V}], New::[{K,V}], Removed::[K])okCalled after a hot code update using the VM’s relup functionality, if the configuration values changed

Supervisors

TriggerCalled ByHandled ByReturnDescription
supervisor:start_link/2-3parent processinit(Arg)<code>ignore | {ok, {SupFlag, [Child]}}</code>Specifies a supervisor. Refer to official documentation

gen_server

TriggerCalled ByHandled ByReturnDescription
gen_server:start_link/3-4supervisorinit(Arg)<code>{ok, State [, Option]} | ignore | {stop, Reason}</code>Set up the initial state of the process
gen_server:call/2-3clienthandle_call(Msg, From, State)<code>{Type::reply | noreply, State [, Option]} | {stop, Reason [, Reply], State}code>Request/response pattern. A message is received and expects an answer
gen_server:cast/2clienthandle_cast(Msg, State)<code>{noreply, State [, Option]} | {stop, Reason, State}</code>Information sent to the process; fire and forget
Pid ! Msgclienthandle_info(Msg, State)same as handle_cast/2Out-of-band messages, including monitor signals and 'EXIT' messages when trappig exit
Setting an Option value to {continue, Val}the server itselfhandle_continue(Val, State)same as handle_cast/2Used to break longer operations into triggerable internal events
gen_server:stop/1,3client or supervisorterminate(Reason, State)term()Called when the process is shutting down willingly or through errors. If the process does not trap exits, this callback may be omitted
sys:get_status/2-3, crash logsclient, the server itself<code>format_status(normal | terminate, [PDict, State])</code>[{data, [{"State", Term}]}]Used to add or remove information that would make it to debugging calls or error logs
N/Asupervisorcode_change(OldVsn, State, Extra){ok, NewState}called to update a stateful process if the proper instructions are given during a hot code upgrade with releases

gen_statem

Process management

TriggerCalled ByHandled ByReturnDescription
gen_statem:start_link/3-4supervisorinit(Arg)<code>{ok, State, Data [, Actions]} | ignore | {stop, Reason}</code>Sets the initial state and data for the state machine
N/Ainternalcallback_mode()<code>[state_functions | handle_event_function [, state_enter]]</code>Defines the type of FSM and whether entering a state triggers a special internal event
gen_statem:stop/1,3client or supervisorterminate(Reason, State, Data)term()Called when the process is shutting down willingly or through errors. If the process does not trap exits, this callback may be omitted
sys:get_status/2-3, crash logsclient, the server itself<code>format_status(normal | terminate, [PDict, State, Data])</code>[{data, [{"State", Term}]}]Used to add or remove information that would make it to debugging calls or error logs
N/Asupervisorcode_change(OldVsn, State, Data, Extra){ok, NewState, NewData}called to update a stateful process if the proper instructions are given during a hot code upgrade with releases

State handling and transitions

Handled by either handle_event/4 or StateName/3 functions, based on the value of callback_mode(). The function signatures are either:

  • handle_event(EventType, EventDetails, State, Data)
  • State(EventType, EventDetails, Data)

If the value of State is not a list, even though callback_mode() defined state_functions, then handle_event/4 will be called. All possible return values for either functions are one of:

  • {next_state, State, Data}
  • {next_state, State, Data, [Actions, ...]}
  • {stop, Reason, Data}
  • {stop, Reason, Data, [Actions, ...]}

Various short forms exist, such as keep_state_and_data, {keep_state, Data}, {repeat_state, Data}, and many more. Refer to the documentation for their content.

The Actions value is any combination of the following list (non-inclusive): postpone, {next_event, EventType, EventDetails}, hibernate, {timeout, Delay, EventDetails}, {state_timeout, Delay, EventDetails}, {reply, From, Reply}, hibernate. Consult the documentation for more options.

TriggerCalled ByEvent TypeEvent DetailsDescription
gen_statem:call/2-3client{call, From}term()Request/response pattern. A message is received and is expected to receive an answer
gen_statem:cast/2clientcastterm()Information must be sent to the process; fire and forget
Pid ! MsgclientinfoMsgOut-of-band messages, including monitor messages and 'EXIT' signals that are trapped
{timeout, T, Msg}Action return valuetimeoutMsgA specific timeout that can be set and received internally when the state machine has not received a new event in T milliseconds
{state_timeout, T, Msg}Action return valuestate_timeoutMsgA specific timeout that can be set and received internally when the state machine has not transitioned to a new different state in T milliseconds
{next_event, internal, Msg}Action return valueinternalMsgInternal messages that can be generated by a state machine wanting to trigger itself without looking like external calls