基础
Hello World 与模块
每个 Erlang 文件都是一个用 -module(name) 声明的模块。导出函数用 -export([name/arity]) 列出。arity(参数个数)是函数标识的一部分——greet/0 和 greet/1 是不同的函数。在 shell 中用 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!变量与原子
变量以大写字母(或 _)开头,且只能单次赋值——一旦绑定就不能改变。对已绑定变量重新匹配实际上是一个断言:X = X+1 会引发 badmatch 错误。原子是以小写字母开头的命名常量;它们存储在全局表中,按身份比较。
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元组与记录
元组是固定大小的容器,写作 {a,b,c}。记录是带命名字段的元组,用 -record 声明。更新记录字段会创建新记录(不可变数据)。第一个元素按惯例是标签原子(如 point 或 person),以便更清晰的模式匹配。
% 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列表与列表推导
列表是用 [Head|Tail] 构建的单向链表。++ 连接(左侧 O(n)),-- 移除元素。列表推导 [Expr || 限定符1, 限定符2, ...] 支持生成器(X <- List)和过滤器(布尔表达式)。它们是转换列表的习惯写法。
[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函数与模式匹配
函数定义为多个由 ; 分隔的子句,以句点终止。子句按顺序尝试,第一个模式和守卫都匹配的被选中。带标签的元组如 {ok, Val} / {error, Reason} 是不用异常表示成功/失败的标准方式。
-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}.映射
映射(自 R17 起)是键值字典。用 => 设置/插入(不存在则创建),用 := 更新(键必须已存在)。模式匹配中只允许 :=。maps:get/2 在键缺失时引发 badarg;用 maps:get/3 获取默认值或 maps:find/2 获取 {ok,_}|error 元组。
% 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.模式匹配与守卫
函数头中的模式匹配
Erlang 通过自上而下匹配参数来选择函数子句。模式可以绑定新变量、对字面量或已绑定变量断言相等,以及用 _ 忽略字段。同样的机制用于 receive、case 和 =。
% 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 表达式
case 求值一个表达式并将其与一系列模式匹配。每个子句可以有守卫。当函数分支依赖计算值而非原始参数时,它是标准控制流工具。除非想对意外输入崩溃,否则总应包含 _ 全匹配子句。
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.守卫
守卫限制子句何时匹配。它们限于已知的内置函数集合,因此总能终止且无副作用。逗号 = AND,分号 = OR。守卫在守卫序列中会被重新求值,因此尽量优先用模式匹配。
% 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!)位语法与二进制
位语法 << Seg1, Seg2, ... >> 按位字段打包/解包二进制。每段为 Value:Size/TypeSpecifier,Type 为 integer|binary|float|bits,可选符号和字节序。这使 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.并发编程
spawn — 启动进程
spawn(M,F,A) 创建新的 BEAM 进程——一个拥有自己堆和邮箱的轻量级绿色线程。进程不共享内存,仅通过消息传递通信。BEAM 可运行数千万个进程;每个约 2-3 KB。spawn/1 接受一个 fun,便于临时工作进程。
% 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 (!) — 消息传递
Pid ! Msg 异步发送消息到接收者的邮箱。投递有保证,且同一对进程之间保持顺序。! 返回消息本身,可实现广播列表。进程可在原子名下注册,这样任何代码都无需持有 PID 即可向其发送消息。
% 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 — 模式匹配消息
receive 按顺序扫描邮箱寻找第一个匹配任何子句的消息;若无匹配则挂起直到有消息到达。after 子句设置超时(0 = 非阻塞检查,infinity = 永久)。常见的服务器模式是尾递归 loop(),在每条消息后调用自身。
% 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 是选择性的:即使有更旧的消息,它也扫描第一个匹配的消息。这非常适合匹配特定回复(用唯一引用 Ref),但过期的非匹配消息会累积并拖慢扫描。gen_server 通过按顺序处理每条消息避免了这个问题。用 make_ref() 创建唯一引用。
% 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.带引用的请求/回复
call 模式将每个请求与唯一引用(make_ref/0)配对。客户端发送 {call, Ref, self(), Request} 并等待用相同 Ref 标记的回复——这让客户端能从邮箱中的其他流量中挑出自己的回复。这是 Erlang RPC 和 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.尾递归与服务器循环
BEAM 进程通过尾递归持有状态:循环以新状态作为最后一个表达式调用自身,因此不会增长栈。这是经典的有状态服务器模式——每个 receive 子句以对 loop/1 的尾调用结束,传入更新后的状态。gen_server 正式化了这一模式。
% 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.链接、监视器与错误
link / unlink — 双向链接
link/1 创建双向链接:任一进程退出,另一个收到退出信号。默认该信号也会使接收者崩溃。设置 process_flag(trap_exit, true) 将信号转为 {'EXIT', From, Reason} 消息,监督器借此检测子进程故障。spawn_link/1 原子地完成两者以避免竞态窗口。
% 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 — 单向监视
monitor(process, Pid) 是单向的:只有调用者收到 'DOWN' 消息,被监视进程不受影响。当你只想观察时(如 call/1 实现需知道服务器是否在调用期间死亡),这比 link 更可取。每个监视器返回唯一 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 信号与原因
exit(Reason) 终止当前进程;exit(Pid, Reason) 向 Pid 发送退出信号。原因很重要:normal 和 shutdown 不传播(链接伙伴存活),其他原因传播并杀死链接伙伴(除非捕获退出)。kill 原因不可捕获——作为终止卡住进程的最后手段。
% 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 与异常
try ... catch Class:Reason 处理三类异常:throw(显式 throw/1,用于非局部返回)、error(运行时错误如 badarg 或 error/1)和 exit(exit/1)。after 子句无论成功失败都运行,用于清理。erlang:get_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 exit监督器模式(手动)
监督器捕获退出,用 spawn_link 启动子进程,收到 {'EXIT', Pid, _} 时重启子进程。这种「任其崩溃」哲学——保持监督器简单、让工作进程快速失败——是 Erlang 容错的核心。实践中使用 OTP supervisor behaviour 而非自己实现。
% 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(简介)
gen_server 行为骨架
实现 -behaviour(gen_server) 告诉编译器检查所有必需的回调函数是否存在。服务器状态流经 init/1 -> handle_call/3(同步)/ handle_cast/2(异步)-> terminate/2。?MODULE 展开为当前模块名;{local, Name} 注册让客户端按原子调用。
-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 与 cast
call 是同步的,阻塞调用者直到服务器回复(或默认 5s 超时后调用者退出)。cast 是异步的,总是立即返回 ok。call 基于带引用的请求/回复模式——它还监视服务器,因此崩溃在调用者中表现为退出。
% 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 与其他回调
handle_info/2 捕获所有非 call/cast 消息——用 Pid ! Msg 发送的任何内容,以及 'DOWN'、超时和套接字数据等系统消息。terminate/2 是释放资源的地方;但在强制杀死时不保证运行。code_change/3 支撑 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}.应用结构(OTP)
OTP 应用是自包含组件,有 .app 资源文件描述其模块、依赖和入口模块(mod)。application behaviour 的 start/2 返回顶层监督器 PID,后者再启动工作进程。Release 将应用打包为可运行系统——这是 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)'相关 Erlang 代码片段
Copy-paste ready code for common tasks.
这篇内容对您有帮助吗?