附錄 1:Erlang/OTP 秘笈

秘笈

2021 年 4 月 28 日

本節包含有助於喚起您記憶力,供在您對基本 Erlang 資料、類型或語法的記憶不太清晰時提醒使用。

資料類型

名稱說明Dialyzer範例語法
整數沒有小數點的數字integer()pos_integer()non_neg_integer()123-21316#01FF2#101011
浮點數具有小數點的數字float()1.0-1.0123.121.0e232
數字浮點數或整數number()1.0, 1
原子文字元、常數,其名稱為值atom()abc'abc'some_atom@erlang'atom with spaces'
布林值原子 truefalseboolean()truefalse
參照唯一的霧視值reference()make_ref()
函數匿名函數fun()fun((ArgType) -> RetType)<code>fun(X) -> X end, fun F(0) -> []; F(N) -> [1 | F(N-1)] end</code>
檔案描述符的不透明類型port()N/A
PID程序識別碼pid()<0.213.0>
元組群組已知的一組元素tuple(){A, B, C}{celsius, 42}、{a, b, c}{ok, {X, Y}}
映射術語字典map()#{KType => VType}#{specific_key := VType}#{a => b, c => d}Existing#{key := Updated}
nil空清單[][]
清單用於術語清單的遞迴結構list()[Type][a, b, c]、<code>[a | [b | [c | []]]]</code>、"a string is a list"
二進制平面位元組序列binary()<<1,2,3,4>><<"a string can be a binary">><<X:Size/type, _Rest/binary>>

術語排序:數字 < 原子 < 參照 < 函數 < 埠 < PID < 元組 < 映射 < nil < 清單 < 二進位

模組和語法

%%% 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: 已捕捉的連結會轉換為訊息,但「終止」原因不可捕捉

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(類型, 引數)<程式碼>{ok, pid()} | {ok, pid(), 狀態}</程式碼>應該啟動根監督
{啟動階段, [{階段, 引數}]}在 app 檔案啟動 app 的核心start_phase(階段, 類型, 引數)<程式碼>ok | {error, 原因}</程式碼>選用。可隔離特定初始化步驟
application:stop/1關閉 appprop_stop(狀態)狀態選用。在關閉監督樹前呼叫
application:stop/1關閉 appstop(狀態)term()app 執行完畢後呼叫,用於清除垃圾
熱碼更新SASL 的釋出處理常式config_change(已變更::[{K,V}], 新的::[{K,V}], 已移除::[K])ok如果組態值變更,則使用虛擬機器 relup 功能進行熱碼更新後呼叫

監督

觸發由以下呼叫由以下處理回傳說明
supervisor:start_link/2-3父處理程序init(引數)<程式碼>ignore | {ok, {SupFlag, [子項]}}</程式碼>指定監督。請參閱官方文件

gen_server

觸發由以下呼叫由以下處理回傳說明
gen_server:start_link/3-4監督init(引數)<程式碼>{ok, 狀態 [, 選項]} | ignore | {stop, 原因}</程式碼>設定處理程序的初始狀態
gen_server:call/2-3用戶端handle_call(訊息, 發訊者, 狀態)<程式碼>{類別::回覆 | 無回覆, 狀態 [, 選項]} | {stop, 原因 [, 回覆], 狀態}</程式碼>請求/回覆模式。接受訊息並期待回答
gen_server:cast/2用戶端handle_cast(訊息, 狀態)<程式碼>{無回覆, 狀態 [, 選項]} | {stop, 原因, 狀態}</程式碼>傳送至處理程序的資訊;發射後即可忘記
Pid ! 訊息用戶端handle_info(訊息, 狀態)handle_cast/2相同帶外訊息,包括截取退出時的監控信號和'EXIT'訊息
選項值設定為{繼續, 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(引數)<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用戶端投遞term()必須將資訊傳送給流程;執行後就忘記
Pid ! 訊息用戶端資訊訊息帶外訊息,包括監控訊息和捕捉的 'EXIT' 訊號
{timeout, T, Msg}Action 回傳值逾時訊息一個特定逾時值,可以在狀態機器在 T 毫秒內沒有收到新事件時設定和內部接收。
{state_timeout, T, Msg}Action 回傳值state_timeout訊息當狀態機在多少毫秒內 T 沒有轉換到新的不同狀態時,就可以設定並接收到特定的逾時
{next_event, internal, Msg}Action 回傳值內部訊息由狀態機產生的內部訊息,希望自行觸發,而且看起來不像是外部呼叫