Skip to content

Erlang Aide-mémoire

Langage fonctionnel orienté concurrence sur la VM BEAM, conçu pour les systèmes distribués tolérants aux pannes.

01

Bases

Hello World et Modules

Chaque fichier Erlang est un module déclaré avec -module(name). Les fonctions exportées sont listées avec -export([name/arity]). L'arité fait partie de l'identité de la fonction — greet/0 et greet/1 sont distinctes. Compilez avec c(Module).

erlang
% hello.erl
-module(hello).
-export([greet/1, greet/0]).

greet() ->
    greet("World").

greet(Name) ->
    io:format("Hello, ~s!~n", [Name]).

% Compile & run in the shell:
%   c(hello).
%   hello:greet("Alice").   %=> Hello, Alice!

Variables et Atomes

Les variables commencent par une majuscule (ou _) et sont à assignation unique. Re-matcher une variable liée est une assertion : X = X+1 lève une erreur badmatch. Les atomes sont des constantes nommées commençant par une minuscule, comparées par identité.

erlang
Name = "Alice".        % variables start with uppercase
Age = 30.               % bound once — single assignment!
% Age = 31.             % ** exception error: no match of RHS

% Atoms start with lowercase (or are quoted)
ok.                    % the ok atom (common return value)
error.                 % another atom
'This is an atom'.     % quoted atom with spaces

true, false, undefined % built-in atoms

% Pattern matching binds and asserts
{ok, Result} = {ok, 42}.   % Result = 42
{error, _} = {error, badarg}.  % _ matches and discards

Tuples et Records

Les tuples sont des conteneurs de taille fixe écrits {a,b,c}. Les records sont des tuples avec champs nommés, déclarés avec -record. Mettre à jour un champ crée un nouveau record (données immuables). Le premier élément est conventionnellement un atome étiquette.

erlang
% Tuples (fixed-size, positional)
Point = {point, 3, 4}.
{point, X, Y} = Point.      % X=3, Y=4
element(2, Point).          %=> point

% Records (syntactic sugar over tuples)
-record(person, {name, age, city = "Unknown"}).

P = #person{name = "Alice", age = 30}.
P#person.name.              %=> "Alice"
P2 = P#person{age = 31}.    % update field
{person, Name, Age, _} = P. % underlying tuple

Listes et Compréhensions

Les listes sont des listes chaînées construites avec [Head|Tail]. ++ concatène (O(n) à gauche), -- retire des éléments. Les compréhensions [Expr || Qualificateur1, ...] supportent générateurs (X <- List) et filtres (expressions booléennes).

erlang
[1,2,3] ++ [4,5].          %=> [1,2,3,4,5]  (append)
[1,2,3] -- [2].            %=> [1,3]        (subtract)
hd([1,2,3]).               %=> 1            (head)
tl([1,2,3]).               %=> [2,3]        (tail)

% Pattern matching on lists
[Head | Tail] = [1,2,3].   % Head=1, Tail=[2,3]
[A,B|Rest] = [1,2,3,4].    % A=1, B=2, Rest=[3,4]

% List comprehensions
[ X*2 || X <- [1,2,3,4] ].          %=> [2,4,6,8]
[ X || X <- [1..10], X rem 2 =:= 0 ]. % evens: [2,4,6,8,10]
[ {A,B} || A <- [1,2], B <- [a,b] ]. % cartesian product

Fonctions et Pattern Matching

Les fonctions sont définies en multiples clauses séparées par ;. Les clauses sont essayées dans l'ordre ; la première dont le motif ET la garde réussissent est utilisée. Les tuples étiquetés {ok, Val} / {error, Reason} signalent succès/échec sans exceptions.

erlang
-module(math_tools).
-export([factorial/1, fib/1, classify/1]).

% Multiple clauses selected by pattern matching
factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).

% Fibonacci with guards
fib(0) -> 0;
fib(1) -> 1;
fib(N) when N > 1 -> fib(N-1) + fib(N-2).

% Tagged returns (common Erlang idiom)
classify(X) when X < 0  -> {negative, X};
classify(0)             -> zero;
classify(X) when X > 0  -> {positive, X}.

Maps

Les maps (depuis R17) sont des dictionnaires clé-valeur. Utilisez => pour définir/insérer et := pour mettre à jour (la clé doit exister). En pattern matching seul := est autorisé. maps:get/2 lève badarg sur clé absente ; utilisez maps:get/3 pour une valeur par défaut.

erlang
% Create
M = #{ name => "Alice", age => 30, role => admin }.

% Lookup
maps:get(name, M).              %=> "Alice"
maps:get(city, M, "Unknown").   % default if missing
{ok, V} = maps:find(age, M).    %=> {ok, 30}

% Update / add (immutable — returns new map)
M2 = M#{ age => 31, city => "Paris" }.

% Pattern match on maps
#{ name := N } = M.            % N = "Alice"  (:= for matching, => for setting)
#{ age := A, role := R } = M.
02

Pattern Matching et Gardes

Pattern Matching dans les Têtes de Fonction

Erlang sélectionne une clause en matchant les arguments de haut en bas. Les motifs peuvent lier des variables, affirmer l'égalité avec des littéraux et ignorer des champs avec _. Le même mécanisme s'applique à receive, case et =.

erlang
% Select clause by structure of argument
handle({ping, From}) -> From ! pong;
handle({echo, Msg})  -> Msg;
handle({add, A, B})  -> A + B;
handle(stop)         -> bye.

% Tuple destructuring
-record(rect, {w, h}).
area(#rect{w=W, h=H}) -> W * H.

% List patterns with literal values
greet_first([H|_]) -> io:format("Hi ~p~n", [H]);
greet_first([])    -> ok.

Expressions case

case évalue une expression et la compare à une séquence de motifs. Chaque clause peut avoir une garde. C'est l'outil de contrôle standard quand la ramification dépend d'une valeur calculée. Incluez toujours une clause _ sauf si vous voulez un crash.

erlang
classify_file(Size) ->
    case Size of
        0              -> empty;
        N when N < 1024 -> small;
        N when N < 1048576 -> medium;
        _              -> large
    end.

% Matching on tagged tuples
Result = case file:read_file("a.txt") of
    {ok, Bin} -> {ok, binary_to_list(Bin)};
    {error, enoent} -> {error, missing};
    {error, Reason} -> {error, Reason}
end.

Gardes

Les gardes restreignent quand une clause correspond. Elles se limitent à un ensemble de fonctions intégrées pour toujours terminer sans effets de bord. Virgule = ET, point-virgule = OU. Préférez le pattern matching aux gardes quand possible.

erlang
% Guards go after 'when' and may use built-in guard tests (BIFs)
max(A, B) when A >= B -> A;
max(_, B)             -> B.

% Multiple guard tests: ',' = AND, ';' = OR
classify(N) when N > 0, N < 100 -> in_range;
classify(N) when N =< 0; N >= 100 -> out_of_range.

% Allowed guard BIFs include comparison, arithmetic, type tests:
% is_integer/1, is_atom/1, is_list/1, is_map/1,
% length/1, map_size/1, abs/1, element/2, hd/1, tl/1
% (No user functions — guards must be side-effect free and terminate!)

Syntaxe de Bits et Binaires

La syntaxe de bits << Seg1, Seg2, ... >> empaquète/dépaquette des binaires par champs de bits. Chaque segment est Value:Size/TypeSpecifier. Cela rend Erlang exceptionnellement bon pour analyser protocoles réseau et formats de fichier.

erlang
% Pack a 16-bit length + 8-bit type + payload
Bin = << Length:16, Type:8, Payload/binary >>.

% Unpack with the same pattern
<< L:16, T:8, Rest/binary >> = Bin.

% Bit fields & endianness
<< N:32/big-unsigned-integer >> = <<0,0,1,1>>.   % N = 261
<< N:32/little-signed-integer >>  = <<1,0,0,0>>. % N = 1

% IPv4 header parsing example
<< Version:4, IHL:4, _TOS:8, TotalLen:16,
   _/binary >> = Packet.
03

Programmation Concurrente

spawn — Démarrer un Processus

spawn(M,F,A) crée un nouveau processus BEAM — un thread vert léger avec son propre heap et sa mailbox. Les processus ne partagent pas de mémoire et communiquent uniquement par passage de messages. BEAM peut gérer des dizaines de millions de processus.

erlang
% spawn/3 starts a new lightweight process
Pid = spawn(Module, Function, ArgsList).
Pid = spawn(fun() -> timer:sleep(1000), io:format("done~n") end).

% Every process has a PID; processes share NO memory
% (BEAM processes are NOT OS threads — millions are possible)

% self() returns the current process's PID
io:format("I am ~p~n", [self()]).

% is_process_alive/1 checks liveness
case is_process_alive(Pid) of
    true  -> ok;
    false -> {error, dead}
end.

send (!) — Passage de Messages

Pid ! Msg envoie asynchronement un message à la mailbox du destinataire. La livraison est garantie et l'ordre préservé entre la même paire de processus. ! retourne le message, permettant les broadcasts. Les processus peuvent être enregistrés sous un nom atome.

erlang
% Send a message: Pid ! Message
Pid ! {hello, self()}.
Pid ! ping.
Pid ! {compute, 1, 2, 3}.

% ! always returns the message itself, so you can broadcast:
[ P ! broadcast || P <- AllPids ].

% Send to a registered process (by atom name)
registered_name ! {request, Ref, self()}.

% Register / unregister a name for a PID
register(server, Pid).
unregister(server).
whereis(server).   %=> Pid | undefined

receive — Messages par Pattern Matching

receive scanne la mailbox dans l'ordre pour le premier message correspondant ; sinon se suspend. La clause after fixe un timeout (0 = non bloquant, infinity = éternel). Un pattern serveur courant est un loop() tail-récursif qui s'appelle lui-même.

erlang
% receive blocks until a matching message arrives
loop() ->
    receive
        {ping, From} ->
            From ! pong,
            loop();
        {echo, Msg} ->
            io:format("echo: ~p~n", [Msg]),
            loop();
        stop ->
            ok
    end.

% receive with a timeout
receive
    {data, X} -> process(X)
after 5000 ->
    {error, timeout}
end.

% Flush mailbox (drain all messages)
flush() ->
    receive _ -> flush() after 0 -> ok end.

Receive Sélectif et Non Sélectif

receive est sélectif : il cherche le premier message correspondant même si des messages plus anciens existent. Idéal pour matcher une réponse spécifique (avec un Ref unique), mais les messages non correspondants s'accumulent. gen_server évite cela.

erlang
% Selective receive: skips non-matching messages
get_reply(Ref) ->
    receive
        {Ref, Reply} -> Reply       % only matches the reply with our Ref
    after 5000 ->
        {error, timeout}
    end.

% Problem: other messages stay in the mailbox, slowing future receives.
% For high-throughput servers use a selective receive library or
% gen_server (which keeps its mailbox clean).

% Non-selective (drain in order):
drain_all() ->
    receive
        Msg -> [Msg | drain_all()]
    after 0 ->
        []
    end.

Requête / Réponse avec Références

Le pattern call associe chaque requête à une référence unique (make_ref/0). Le client envoie {call, Ref, self(), Request} et attend une réponse étiquetée avec le même Ref. C'est la base de gen_server:call.

erlang
% Client: send a tagged request, await the matching reply
call(Server, Request) ->
    Ref = make_ref(),
    Server ! {call, Ref, self(), Request},
    receive
        {Ref, Reply} -> Reply
    after 5000 ->
        exit(timeout)
    end.

% Server side:
handle({call, Ref, From, Request}, State) ->
    Reply = do_work(Request),
    From ! {Ref, Reply},
    State.

Récursivité Terminale et Boucle Serveur

Les processus BEAM maintiennent l'état par récursivité terminale : la boucle s'appelle avec le nouvel état comme dernière expression, sans croître la pile. Chaque clause receive se termine par un appel terminal à loop/1 avec l'état mis à jour.

erlang
% A counter server — each message mutates state via recursion
start_counter(Init) -> spawn(fun() -> counter_loop(Init) end).

counter_loop(N) ->
    receive
        inc      -> counter_loop(N + 1);
        dec      -> counter_loop(N - 1);
        {get, From} -> From ! {value, N}, counter_loop(N);
        reset    -> counter_loop(0)
    end.

% Caller:
Pid = start_counter(0).
Pid ! inc.
Pid ! {get, self()}.
receive {value, V} -> io:format("count = ~p~n", [V]) end.
05

OTP gen_server (Brève)

Squelette du Behaviour gen_server

Implémenter -behaviour(gen_server) fait vérifier par le compilateur les fonctions callback requises. L'état passe par init/1 -> handle_call/3 (synchrone) / handle_cast/2 (asynchrone) -> terminate/2. {local, Name} permet d'appeler par atome.

erlang
-module(counter).
-behaviour(gen_server).
-export([start_link/0, inc/0, get/0]).
-export([init/1, handle_call/3, handle_cast/2,
         handle_info/2, terminate/2, code_change/3]).

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
inc()        -> gen_server:cast(?MODULE, inc).
get()        -> gen_server:call(?MODULE, get).

init(N)              -> {ok, N}.
handle_call(get, _F, N)  -> {reply, N, N}.
handle_cast(inc, N)      -> {noreply, N + 1}.
handle_info(_, N)        -> {noreply, N}.
terminate(_, _)          -> ok.
code_change(_, N, _)     -> {ok, N}.

call vs cast

call est synchrone et bloque l'appelant jusqu'à la réponse (ou timeout 5s). cast est asynchrone et retourne toujours ok immédiatement. call repose sur le pattern requête/réponse avec référence.

erlang
% Synchronous call — waits for reply, crashes on server death
{ok, Value} = gen_server:call(Server, {get, Key}).

% Asynchronous cast — fire and forget
ok = gen_server:cast(Server, {put, Key, Value}).

% Call supports timeout and monitor under the hood:
gen_server:call(Server, Req, 5000).

% Server replies in handle_call:
handle_call({get, Key}, From, State) ->
    {reply, maps:get(Key, State), State}.

% handle_cast never replies:
handle_cast({put, K, V}, State) ->
    {noreply, maps:put(K, V, State)}.

handle_info et Autres Callbacks

handle_info/2 capture les messages non call/cast — tout Pid ! Msg, 'DOWN', timeouts et données socket. terminate/2 libère les ressources (non garanti sur kill brutal). code_change/3 permet le célèbre hot code upgrade sans interruption.

erlang
% handle_info/2 handles messages NOT from call/cast:
%   - plain messages from other processes (Pid ! Msg)
%   - 'DOWN' messages from monitors
%   - timeouts sent by gen_server:start(..., Timeout)
handle_info({'DOWN', Ref, process, Pid, Reason}, State) ->
    io:format("monitored ~p died: ~p~n", [Pid, Reason]),
    {noreply, State};
handle_info({tcp, Socket, Data}, State) ->
    %% handle incoming socket data
    {noreply, State};
handle_info(Info, State) ->
    {noreply, State}.

% terminate/2 runs on shutdown — close fds, flush, etc.
terminate(Reason, State) ->
    io:format("shutting down: ~p~n", [Reason]),
    ok.

% code_change/3 enables hot code upgrade.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

Structure d'Application (OTP)

Une application OTP est un composant autonome avec un .app décrivant modules, dépendances et module d'entrée (mod). start/2 retourne le PID du superviseur racine. Les releases empaquettent les applications en un système exécutable.

erlang
% myapp.app — application resource file
{application, myapp,
 [{description, "Demo app"},
  {vsn, "1.0.0"},
  {modules, [myapp_app, myapp_sup, counter]},
  {registered, [myapp_sup]},
  {applications, [kernel, stdlib]},
  {mod, {myapp_app, []}},
  {env, [{port, 4000}]}
 ]}.

% myapp_app.erl — application behaviour
-module(myapp_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) -> myapp_sup:start_link().
stop(_State) -> ok.

% Start everything:
%   application:start(myapp).
% Or with release:  erl -eval 'application:ensure_all_started(myapp)'

Was this helpful?