Skip to content

Erlang Spickzettel

Nebenläufigkeitsorientierte funktionale Sprache auf der BEAM-VM, gebaut für fehlertolerante verteilte Systeme.

01

Grundlagen

Hello World und Module

Jede Erlang-Datei ist ein mit -module(name) deklariertes Modul. Exportierte Funktionen werden mit -export([name/arity]) aufgelistet. Die Stelligkeit (Anzahl Argumente) ist Teil der Funktionsidentität — greet/0 und greet/1 sind verschieden. Kompilieren mit 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!

Variablen und Atome

Variablen beginnen groß (oder _) und sind Einmalbindung — einmal gebunden, nicht änderbar. Erneutes Matchen einer gebundenen Variable ist eine Behauptung: X = X+1 löst badmatch aus. Atome sind kleingeschriebene Konstanten, verglichen nach Identität.

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

Tupel und Records

Tupel sind fixgroße Container der Form {a,b,c}. Records sind Tupel mit benannten Feldern, deklariert mit -record. Feldaktualisierung erzeugt ein neues Record (unveränderliche Daten). Das erste Element ist konventionell ein Tag-Atom.

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

Listen und List Comprehensions

Listen sind einfach verkettet, aufgebaut mit [Head|Tail]. ++ verkettet (O(n) links), -- entfernt Elemente. Comprehensions [Expr || Qualifikator1, ...] unterstützen Generatoren (X <- List) und Filter (Boolesche Ausdrücke).

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

Funktionen und Pattern Matching

Funktionen sind mehrere durch ; getrennte Klauseln. Klauseln werden der Reihe nach probiert; die erste mit passendem Muster UND Guard gewinnt. Getaggte Tupel {ok, Val} / {error, Reason} signalisieren Erfolg/Misserfolg ohne 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

Maps (seit R17) sind Schlüssel-Wert-Wörterbücher. => setzt/fügt ein, := aktualisiert (Schlüssel muss existieren). Beim Pattern Matching ist nur := erlaubt. maps:get/2 wirft badarg bei fehlendem Schlüssel; maps:get/3 gibt einen Default zurück.

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 und Guards

Pattern Matching in Funktionsköpfen

Erlang wählt eine Klausel durch Pattern Matching der Argumente von oben nach unten. Muster können Variablen binden, Gleichheit mit Literalen behaupten und Felder mit _ ignorieren. Derselbe Mechanismus gilt für receive, case und =.

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.

case-Ausdrücke

case wertet einen Ausdruck aus und vergleicht mit einer Folge von Mustern. Jede Klausel kann einen Guard haben. Standardwerkzeug für Kontrollfluss, wenn Verzweigung von einem berechneten Wert abhängt. Includes immer eine _-Klausel.

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.

Guards

Guards beschränken, wann eine Klausel passt. Sie sind auf bekannte BIFs begrenzt, damit sie immer terminieren und nebeneffektfrei sind. Komma = UND, Semikolon = ODER. Pattern Matching vor Guards bevorzugen.

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!)

Bit-Syntax und Binaries

Die Bit-Syntax << Seg1, Seg2, ... >> packt/entpackt Binaries nach Bitfeldern. Jedes Segment ist Value:Size/TypeSpecifier. Erlang ist dadurch hervorragend im Parsen von Netzwerkprotokollen und Dateiformaten.

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

Nebenläufige Programmierung

spawn — Prozess starten

spawn(M,F,A) erzeugt einen neuen BEAM-Prozess — einen leichten Green Thread mit eigenem Heap und eigener Mailbox. Prozesse teilen keinen Speicher und kommunizieren nur per Message Passing. BEAM kann Zehnmillionen Prozesse verwalten.

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 (!) — Message Passing

Pid ! Msg sendet asynchron eine Nachricht an die Mailbox des Empfängers. Zustellung ist garantiert, Reihenfolge bleibt zwischen demselben Paar erhalten. ! gibt die Nachricht zurück, enabling Broadcasts. Prozesse können unter einem Atom-Namen registriert werden.

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 — Nachrichten per Pattern Matching

receive durchsucht die Mailbox nach der ersten passenden Nachricht; sonst suspendiert es. Die after-Klausel setzt einen Timeout (0 = nichtblockierend, infinity = ewig). Ein Server-Pattern ist eine endrekursive loop(), die sich selbst aufruft.

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.

Selektives und nichtselektives Receive

receive ist selektiv: es sucht die erste passende Nachricht, auch wenn ältere existieren. Ideal zum Matchen einer bestimmten Antwort (mit eindeutigem Ref), aber nichtpassende Nachrichten sammeln sich an. gen_server vermeidet dies.

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.

Request / Reply mit Referenzen

Das call-Pattern paart jede Anfrage mit einer eindeutigen Referenz (make_ref/0). Der Client sendet {call, Ref, self(), Request} und wartet auf eine mit demselben Ref getaggte Antwort. Grundlage von 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.

Endrekursion und Serverschleife

BEAM-Prozesse halten Zustand per Endrekursion: die Schleife ruft sich mit dem neuen Zustand als letztem Ausdruck auf, ohne den Stack wachsen zu lassen. Jede receive-Klausel endet mit einem Endruf zu loop/1.

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 (Kurz)

gen_server-Behaviour-Gerüst

-behaviour(gen_server) prüft alle nötigen Callbacks. Der Zustand fließt durch init/1 -> handle_call/3 (synchrone) / handle_cast/2 (asynchrone) -> terminate/2. {local, Name} erlaubt atom-basierte Aufrufe.

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 ist synchron und blockt den Aufrufer bis zur Antwort (oder Timeout 5s). cast ist asynchron und gibt sofort ok zurück. call basiert auf dem Request/Reply-with-Reference-Pattern.

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 und weitere Callbacks

handle_info/2 fängt alle Nicht-call/cast-Nachrichten — Pid ! Msg, 'DOWN', Timeouts, Socket-Daten. terminate/2 gibt Ressourcen frei (nicht garantiert bei brutalem Kill). code_change/3 ermöglicht Hot Code Upgrades.

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

Anwendungsstruktur (OTP)

Eine OTP-Anwendung ist eigenständig mit einer .app-Datei für Module, Abhängigkeiten und Einstiegsmodul (mod). start/2 gibt die PID des Root-Supervisors zurück. Releases bündeln Anwendungen zu einem lauffähigen System.

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?