附录 1:Erlang/OTP 速查表

速查表

2021年4月28日

本节包含各种提示,如果您对基本的 Erlang 数据、类型或语法不太熟悉,可以帮助您回忆。

数据类型

名称描述Dialyzer示例语法
整数 (integer)没有小数点的数字integer(), pos_integer(), non_neg_integer()1, 2, 3, -213, 16#01FF, 2#101011
浮点数 (float)带有小数点的数字float()1.0, -1.0, 123.12, 1.0e232
数字 (number)浮点数或整数number()1.0, 1
原子 (atom)文字常量,使用其自身名称表示值atom()abc, 'abc', some_atom@erlang, 'atom with spaces'
布尔值 (boolean)原子 truefalseboolean()true, false
引用 (reference)唯一的不透明值reference()make_ref()
函数 (fun)匿名函数fun(), fun((ArgType) -> RetType)<code>fun(X) -> X end, fun F(0) -> []; F(N) -> [1 | F(N-1)] end</code>
端口 (port)文件描述符的不透明类型port()N/A
进程 ID (pid)进程标识符pid()<0.213.0>
元组 (tuple)对一组已知元素进行分组tuple(), {A, B, C}{celsius, 42}, {a, b, c}, {ok, {X, Y}}
映射 (map)项的字典map(), #{KType => VType}, #{specific_key := VType}#{a => b, c => d}, Existing#{key := Updated}
空 (nil)空列表[][]
列表 (list)项列表的递归结构list(), [Type][a, b, c], <code>[a | [b | [c | []]]]</code>, "a string is a list"
二进制 (binary)扁平的字节序列binary()<<1,2,3,4>>, <<"a string can be a binary">>, <<X:Size/type, _Rest/binary>>

项排序:number < atom < reference < fun < port < pid < tuple < map < nil < list < binary

模块和语法

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

进程和信号

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

以及链接和监视器的语义,以图表形式表示

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

图 1: 监视器是单向的信息信号,并且它们会堆叠

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

图 2: 未捕获的链接是双向的,会杀死另一个进程,除非原因是“正常”

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

图 3: 捕获的链接会转换为消息,除了不可捕获的“kill”原因

由于监督机制,OTP 进程具有略微不同的语义

Figure 4: Untrapped links work the same for OTP

图 4: 未捕获的链接对 OTP 来说工作方式相同

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

图 5: 当进程的父进程死亡时,捕获的链接会以特殊方式运行

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

图 6: 监督器根据终止原因以不同的方式记录信息

行为

此处未列出所有 OTP 行为,仅列出了最常用的行为。

应用程序

触发器调用方处理方返回值描述
application:start/1-2客户端或启动虚拟机start(Type, Args)<code>{ok, pid()} | {ok, pid(), State}</code>应启动根监督器
{start_phases, [{Phase, Args}]} 在 app 文件中kernel 启动应用程序start_phase(Phase, Type, Args)<code>ok | {error, Reason}</code>可选。可以隔离初始化的特定步骤
application:stop/1应用程序关闭prop_stop(State)State可选。在关闭监督树之前调用
application:stop/1应用程序关闭stop(State)term()应用程序运行完成后调用,以清理内容
热代码更新SASL 的发布处理程序config_change(Changed::[{K,V}], New::[{K,V}], Removed::[K])ok使用虚拟机的 relup 功能进行热代码更新后调用,如果配置值已更改

监督器

触发器调用方处理方返回值描述
supervisor:start_link/2-3父进程init(Arg)<code>ignore | {ok, {SupFlag, [Child]}}</code>指定监督器。请参阅官方文档

gen_server

触发器调用方处理方返回值描述
gen_server:start_link/3-4监督器init(Arg)<code>{ok, State [, Option]} | ignore | {stop, Reason}</code>设置进程的初始状态
gen_server:call/2-3客户端handle_call(Msg, From, State)<code>{Type::reply | noreply, State [, Option]} | {stop, Reason [, Reply], State}code>请求/响应模式。接收消息并期望得到答案
gen_server:cast/2客户端handle_cast(Msg, State)<code>{noreply, State [, Option]} | {stop, Reason, State}</code>发送到进程的信息;即发即忘
Pid ! Msg客户端handle_info(Msg, State)handle_cast/2 相同带外消息,包括监视器信号和捕获退出时的 'EXIT' 消息
Option 值设置为 {continue, Val}服务器本身handle_continue(Val, State)handle_cast/2 相同用于将较长的操作分解为可触发的内部事件
gen_server:stop/1,3客户端或监督器terminate(Reason, State)term()进程自愿或因错误而关闭时调用。如果进程不捕获退出,则可以省略此回调
sys:get_status/2-3, 崩溃日志客户端,服务器本身<code>format_status(normal | terminate, [PDict, State])</code>[{data, [{"State", Term}]}]用于添加或删除信息,这些信息将添加到调试调用或错误日志中
N/A监督器code_change(OldVsn, State, Extra){ok, NewState}如果在使用发布进行热代码升级时提供了正确的指令,则调用此函数以更新有状态的进程

gen_statem

进程管理

触发器调用方处理方返回值描述
gen_statem:start_link/3-4监督器init(Arg)<code>{ok, State, Data [, Actions]} | ignore | {stop, Reason}</code>设置状态机的初始状态和数据
N/A内部callback_mode()<code>[state_functions | handle_event_function [, state_enter]]</code>定义 FSM 的类型以及进入状态是否会触发特殊的内部事件
gen_statem:stop/1,3客户端或监督器terminate(Reason, State, Data)term()进程自愿或因错误而关闭时调用。如果进程不捕获退出,则可以省略此回调
sys:get_status/2-3, 崩溃日志客户端,服务器本身<code>format_status(normal | terminate, [PDict, State, Data])</code>[{data, [{"State", Term}]}]用于添加或删除信息,这些信息将添加到调试调用或错误日志中
N/A监督器code_change(OldVsn, State, Data, Extra){ok, NewState, NewData}如果在使用发布进行热代码升级时提供了正确的指令,则调用此函数以更新有状态的进程

状态处理和转换

handle_event/4StateName/3 函数处理,具体取决于 callback_mode() 的值。函数签名为:

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

如果 State 的值不是列表,即使 callback_mode() 定义了 state_functions,也会调用 handle_event/4。这两个函数的所有可能返回值都是以下之一:

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

存在各种简写形式,例如 keep_state_and_data{keep_state, Data}{repeat_state, Data} 等。请参阅文档以了解其内容。

Actions 值是以下列表的任意组合(非穷举):postpone{next_event, EventType, EventDetails}hibernate{timeout, Delay, EventDetails}{state_timeout, Delay, EventDetails}{reply, From, Reply}hibernate。请查阅文档以获取更多选项。

触发器调用方事件类型事件详细信息描述
gen_statem:call/2-3客户端{call, From}term()请求/响应模式。接收消息并期望收到答案
gen_statem:cast/2客户端castterm()必须将信息发送到进程;即发即忘
Pid ! Msg客户端infoMsg带外消息,包括监视器消息和捕获的 'EXIT' 信号
{timeout, T, Msg}Action 返回值timeoutMsg可以在状态机在 T 毫秒内未收到新事件时内部设置和接收的特定超时
{state_timeout, T, Msg}Action 返回值state_timeoutMsg可以在状态机在 T 毫秒内未转换到新的不同状态时内部设置和接收的特定超时
{next_event, internal, Msg}Action 返回值内部Msg状态机想要触发自身而不像外部调用时可以生成的内部消息