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).
% 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.
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 discardsTupel 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.
% 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 tupleListen 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).
[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 productFunktionen 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.
-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.
% 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.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 =.
% 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.
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.
% 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.
% 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.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.
% 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.
% 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 | undefinedreceive — 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.
% 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.
% 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.
% 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.
% 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.Links, Monitore und Fehler
link / unlink — Bidirektionaler Link
link/1 erzeugt einen bidirektionalen Link: stirbt einer, erhält der andere ein Exit-Signal. Standardmäßig crash das auch den Empfänger. process_flag(trap_exit, true) wandelt Signale in {'EXIT', From, Reason}-Nachrichten um. spawn_link/1 macht beides atomar.
% link/1 links current process to another
Pid = spawn(fun() -> ... end),
link(Pid).
% If either dies, the other gets an exit signal.
% Default behavior: the linked partner also exits.
% spawn_link does both atomically:
Pid = spawn_link(fun() -> ... end).
% Trap exits to convert death signals into messages:
process_flag(trap_exit, true).
receive
{'EXIT', From, Reason} -> io:format("~p died: ~p~n", [From, Reason])
end.
unlink(Pid). % remove the link (best-effort)monitor / demonitor — Einweg-Monitor
monitor(process, Pid) ist einseitig: nur der Aufrufer erhält eine 'DOWN'-Nachricht. Bei reiner Beobachtung link vorzuziehen. Jeder Monitor liefert ein eindeutiges Ref.
% monitor/1: one-way — we observe Pid, but it doesn't observe us
Ref = monitor(process, Pid).
% If Pid dies, we receive:
% {'DOWN', Ref, process, Pid, Reason}
receive
{'DOWN', Ref, process, Pid, normal} -> ok;
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
end.
% demonitor/1 removes the monitor
demonitor(Ref).
% demonitor/2 with [flush] also drains any pending 'DOWN' message
demonitor(Ref, [flush]).exit-Signale und Gründe
exit(Reason) beendet den aktuellen Prozess; exit(Pid, Reason) sendet ein Signal an Pid. Der Grund zählt: normal und shutdown propagieren nicht, andere töten die Verlinkten (außer sie trappen). kill ist untrappbar — letztes Mittel.
% Send an exit signal explicitly:
exit(Pid, Reason). % tell Pid to exit with Reason
% Terminate the current process:
exit(Reason). % Reason != normal kills linked partners
% exit/2 with the atom 'kill' is untrappable:
exit(Pid, kill). % Pid dies even if it traps exits.
% (Only the kernel-supervised 'kill' reason cannot be trapped.)
% Reasons:
% normal -> linked partners do NOT die
% shutdown -> linked partners do NOT die (used in graceful stop)
% {shutdown, Term} -> graceful with reason, also non-propagating
% anything else -> linked partners also die (unless trapping)try / catch und Exceptions
try ... catch Class:Reason behandelt drei Klassen: throw (explizites throw/1), error (Laufzeitfehler), exit (exit/1). Die after-Klausel läuft immer zur Bereinigung. erlang:get_stacktrace() liefert die Stacktrace.
try
{ok, Bin} = file:read_file(Path),
binary_to_list(Bin)
catch
throw:Term -> {thrown, Term};
error:Reason -> {error, Reason, erlang:get_stacktrace()};
exit:Reason -> {exit, Reason}
after
% always runs (cleanup), regardless of success/failure
file:close(Fd)
end.
% Raise exceptions:
throw({bad, Value}). % catchable as throw
erlang:error(badarg). % catchable as error
exit(timeout). % catchable as exitSupervisor-Pattern (Manuell)
Ein Supervisor fängt Exits ab, startet sein Kind mit spawn_link und startet es bei {'EXIT', Pid, _} neu. Die Philosophie «let it crash» — einfacher Supervisor, schnell scheiternde Worker — ist das Herz von Erlangs Fehlertoleranz.
% A minimal supervisor: start a child, restart on death
start_sup(Mod) ->
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> Mod:run() end),
sup_loop(Mod, Pid).
sup_loop(Mod, Pid) ->
receive
{'EXIT', Pid, Reason} ->
io:format("child died ~p, restarting~n", [Reason]),
NewPid = spawn_link(fun() -> Mod:run() end),
sup_loop(Mod, NewPid);
{stop, From} ->
exit(Pid, shutdown),
From ! ok
end.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.
-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.
% 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.
% 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.
% 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)'Verwandte Erlang-Snippets
Copy-paste ready code for common tasks.
Ping-Pong-Prozesse
Zwei Prozesse tauschen Nachrichten mit spawn und receive aus.
gen_server-Zähler
Baut einen zustandsbehafteten Server mit dem gen_server-Behaviour.
Supervisor-Baum
Definiert einen Supervisor, der Worker startet und bei Absturz neu startet.
Selektives Receive mit Referenzen
Extrahiert eine spezifische Antwort aus vielen Nachrichten mit einer eindeutigen Referenz.
Links, Exit-Trapping und „Let It Crash“
Verwendet link und trap_exit, um Prozessabstürze zu erkennen und zu beheben.
Zustandsbehafteter Server via Endrekursion
Hält veränderlichen Zustand in einem Prozess durch rekursive Aufrufe.
Parallele Map mit rpc:pmap
Wendet eine Funktion parallel über Prozesse auf jedes Listenelement an.
Hot Code Upgrade
Lädt Modulcode neu, ohne das laufende System zu stoppen.
Was this helpful?