# Erlang/OTP チートシート

> Source: https://www.ymotongpoo.com/works/adopting-erlang-ja/cheat_sheets/


この節には、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 | floatまたはintegerのどちらか | `number()` | `1.0`, `1` |
| atom | 値そのものが名前になるリテラル・定数 | `atom()` | `abc`, `'abc'`, `some_atom@erlang`, `'atom with spaces'` |
| boolean | atomの`true`または`false` | `boolean()` | `true`, `false` |
| reference | 一意で不透明な値 | `reference()` | `make_ref()` |
| fun | 無名関数 | `fun()`, `fun((ArgType) -> RetType)` | `fun(X) -> X end, fun F(0) -> []; F(N) -> [1 \| F(N-1)] end` |
| port | ファイルディスクリプタ用の不透明な型 | `port()` | N/A |
| 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]`, `[a \| [b \| [c \| []]]]`, `"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
```

## モジュールと構文

```erlang
%%% これはモジュールレベルのコメントです
%%% @doc このタグは公式のEDocドキュメントを含みます。
%%% 参照する人にとって便利な場合があります
%%% @end
%%% rebar3 edoc でドキュメントを生成します

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% モジュール属性から始めましょう %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% これは属性または関数固有のコメントです。
%% 属性は `-' で始まり、関数は文字で始まります。
%% このファイルは `sample.erl' として保存する必要があります
-module(sample).

%% 関数はName/Arityの形式で記述され、
%% `-export([...]).' モジュール属性を通してエクスポートする必要があります
-export([f/0, f/1]).
-export([x/0]).         % 複数のexport属性を持つこともできます

%% 別のモジュールから関数を「import」することもできますが、
%% わかりやすさのため(そして名前空間がないため)
%% 実際にそうする人はほとんどいません
-import(module, [y/0]).

%% .hrlファイルにはヘッダが含まれ、モジュール内に直接
%% importされます。
%% 以下はsrc/内のプライベートなヘッダファイル、または
%% 現在のアプリのinclude/内の公開ヘッダファイルをincludeします
-include("some_file.hrl").
%% 以下は別のアプリケーションのinclude/ファイルにある
%% 公開ヘッダファイルをincludeします
-include_lib("appname/include/some_file.hrl").

%% 実装するインタフェースを指定します
-behaviour(gen_server).

%% レコードを定義します(コンパイラが特別に扱うタプルです)
-record(struct, {key = default :: term(),
                 other_key     :: undefined | integer()}).

%% ただのCスタイルのマクロです
-define(VALUE, 42).        % このモジュール内の?VALUEは`42'になります
-define(SQUARE(X), (X*X)). % 関数マクロ
-define(DBG(Call),         % 凝ったデバッグマクロ: ?DBG(2 + 2)
        io:format("DBG: ~s (~p): ~p~n",
                  [??Call, {?MODULE, ?LINE}, Call])).

%% 条件分岐
-ifdef(MACRO_NAME).        % 逆は: -ifndef(MACRO_NAME).
-define(OTHER_MACRO, ok).
-else.                     % 他の選択肢: -elif(NAME).
-define(MACRO_NAME, ok).
-endif.

%% 型定義
-type my_type() :: number() | boolean().
-type my_container(T) :: {[T], [T], my_type(), mod:type()}
-export_type([my_type/0, my_container/1]).

%% カスタム属性も定義できます
-my_attribute(hello_there).
-author("Duke Erlington").

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ここからはコードと関数のためのモジュールです %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @doc atomを返す引数0個の関数
-spec f() -> term(). % 任意のspec
f() -> ok.

-spec f(number()) -> float().
f(N) -> N + 1.0.

%% 節を使ったパターンマッチング
x([]) -> [];  % リストの基底となる再帰節
x([_H|T] -> [x | T]. % リストの要素をatom `x' に置き換えます

%% @private 変数束縛のルール
same_list(X = [_|_], X) -> true;
same_list([], []) -> true;
same_list(_, _) -> false.

%% 言語の演算子
operators(X, Y) ->
    +X, -Y, % 単項演算子
    X + Y, X - Y, X * Y, X / Y,   % 任意の数値
    X div Y, X rem Y,             % 整数専用
    X band Y, X bor Y, X bxor Y,  % ビット演算子
    X bsl Y, X bsr L,             % ビットシフト
    not X,                        % 論理否定
    X andalso Y, X orelse Y,      % ショートサーキットの論理演算子
    X < Y, X > Y, X >= Y, X =< Y, % 比較
    X == Y, X /= Y,               % 等価(floatとintでも==)
    X =:= Y, X =/= Y,             % 厳密な等価(floatとintは=/=)
    X ++ Y, X -- Y,               % YをXに追加、XからYを削除
    X ! Y.                        % プロセスXにメッセージYを送信

%% ガードの使用。有効なガード式は
%% erlang.org/doc/reference_manual/expressions.html#guard-sequences を参照
comfortable({celsius, X}) when X >= 18, X =< 26 -> % AND節
    true;
comfortable({celsius, _}) ->
    false.

uncomfortable({celsius, X}) when X =< 18; X >= 26 -> % OR節
    true;
uncomfortable({celsius, _}) ->
    false.

%% 'andalso' と 'orelse' の違い
conds(X) when (is_number(X) orelse is_integer(X))
               andalso X < 9 ->
    %% (A AND B) OR C と等価
    true;
conds(X) when is_number(X); is_integer(X), X < 9 ->
    %% - , や ; では括弧が使えません
    %% - A OR (B AND C) と等価です
    true;
conds(T) when element(1, T) == celsius; is_integer(T) ->
    %% element/2はタプルから要素を取り出します。`T'がタプルで
    %% なければ呼び出しは失敗し、代わりに`is_integer/1'が
    %% 試されます
    true;
conds(T) when element(1, T) == celsius orelse is_integer(T) ->
    %% これは決して動きません。element/2が失敗すると
    %% `orelse'式全体が失敗し、`is_integer/1'はスキップされます
    true.

%% 条件分岐
conditional('if', Light) ->
    if Light == red -> stop;
       Light == green; Light == yellow -> go_fast;
       true -> burnout % else節です！
    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.

%% リスト内包表記とバイナリ内包表記
comp(ListA, ListB) ->
    [X*X || X <- ListA, X rem 2 == 0], % 偶数を2乗します
    [{X,Y} || X <- ListA, Y <- ListB], % すべての組み合わせ
    << <<X:8>> || X <- ListA >>.       % リストをバイト列に変換します
comp(BinA, BinB) -> % 今度はバイナリで
    << <<X*X:32>> || <<X:8>> <= Bin, X rem 2 == 0 >>,
    [{X,Y} || <<X:32>> <= BinA, <<Y:8>> <= BinB],
    [X || <<X:8>> <= BinA].

%% 無名関数と高階関数
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), % 直接呼び出すこともできます
    lists:map(fun(X) -> X*X end, [1,2,3,4,5]).

try_catch() ->
    try
        some_call(),     % この呼び出しの例外も捕捉されます
        {ok, val},       % パターンマッチしたい一般的な正常値
        {error, reason}, % パターンマッチしたい一般的な異常値
        % これらの式はいずれも実行フローを中断させます
        throw(reason1), % ローカルでないreturn、内部例外
        error(reason2), % 修正不能なエラー
        exit(reason3)   % プロセスを終了させるべき場合
    of  % このセクションは任意です。ここでの例外は捕捉されません
        {ok, V} ->
            do_something(V),
            try_catch(); % スタックを溢れさせずに安全に再帰します
        {error, R} ->
            {error, R} % そのまま返します
    catch % このセクションは任意です。さまざまなパターンがあります
        throw:reason1 -> handled;
        reason2 -> oops; % `throw'は暗黙の型なので決してマッチしません
        error:reason2 -> handled;
        exit:reason3 -> handled;
        throw:_ -> wildcard_throws;
        E:R when is_error(E) -> any_error;
        _:_:S -> {stacktrace, S}; % スタックトレースを取り出します
    after -> % 任意の'finally'ブロックです
        finally
    end.
```

## プロセスとシグナル

```erlang
%% 新しいプロセスを開始します
Pid = spawn(fun() -> some_loop(Arg) end)
Pid = spawn('name@remote.host', fun() -> some_loop(Arg) end)
Pid = spawn(some_module, some_loop, [Arg])
Pid = spawn('name@remote.host', some_module, some_loop, [Arg])
%% リンクされたプロセスをspawnします
Pid = spawn_link(...) % spawn/1-4と同じく引数は1〜4個
%% 監視付きのプロセスを不可分にspawnします
{Pid, Ref} = spawn_monitor(fun() -> some_loop(Arg) end)
{Pid, Ref} = spawn_monitor(some_module, some_loop, [Arg])
%% 凝ったオプション付きでspawnします
spawn_opt(Fun, Opts)
spawn_opt(Node, Fun, Opts)
spawn_opt(Mod, Fun, Args, Opts)
spawn_opt(Node, Mod, Fun, Args, Opts)
%% オプションは次のspecに従う必要があります。多くは高度な用途向けです
[link | monitor |
 {priority, low | normal | high | max} |    % 触らないこと
 {fullsweep_after, integer() >= 0} |        % フルGC
 {min_heap_size, Words :: integer() >= 0} | % 性能チューニング
 {min_bin_heap_size, Words} |
 {max_heap_size,                    % これを超えるとプロセスが
   Words |                          % killされる可能性があるヒープサイズ。
   #{size => integer() >= 0,        % 最大キューサイズを間接的に
     kill => boolean(),             % 設定するのに使います
     error_logger => boolean()}}

%% プロセスにexitシグナルを送信します
exit(Pid, Reason)

%% メッセージを受信します
receive
    Pattern1 when OptionalGuard1 ->
        Expression1;
    Pattern2 when OptionalGuard2 ->
        Expression2
after Milliseconds -> % 任意
    Expression
end

%% プロセスに名前を付けます
true = register(atom_name, Pid)
true = unregister(atom_name)
Pid | undefined = whereis(atom_name)

%% 監視
Ref = erlang:monitor(process, Pid)
true = erlang:demonitor(Ref)
true | false = erlang:demonitor(Ref, [flush | info])

%% リンク
link(Pid)
unlink(Pid)
process_info(trap_exit, true | false)
```

リンクと監視のセマンティクスを図で示すと、次のようになります。

![監視は一方向の情報伝達シグナルであり、積み重なります](sig_mon_sm.png)

![捕捉されていないリンクは双方向であり、理由が'normal'でない限り相手のプロセスをkillします](sig_linked_notrap_sm.png)

![捕捉されたリンクはメッセージに変換されます。ただし捕捉不能な'kill'という理由は例外です](sig_linked_trap_sm.png)

OTPのプロセスは、supervisionのちょっとした仕掛けによりセマンティクスがわずかに異なります。

![捕捉されていないリンクはOTPでも同様に動作します](sig_otp_notrap_sm.png)

![捕捉されたリンクは、プロセスの親自身が終了する場合に特別な振る舞いをします](sig_otp_trap_sm.png)

![supervisorは終了理由によってログの出し方を変えます](sig_otp_own_sm.png)

## ビヘイビア

ここにすべてのOTPビヘイビアが載っているわけではなく、もっとも頻繁に使われるものだけを挙げています。

### アプリケーション

| Trigger | Called By | Handled By | Return | 説明 |
|---|---|---|---|---|
| `application:start/1-2` | client or booting VM | `start(Type, Args)` | `{ok, pid()} \| {ok, pid(), State}` | ルートのsupervisorを起動する必要があります |
| `{start_phases, [{Phase, Args}]}` in app file | `kernel` booting the app | `start_phase(Phase, Type, Args)` | `ok \| {error, Reason}` | 任意。初期化の特定のステップを分離できます |
| `application:stop/1` | app shutting down | `prep_stop(State)` | `State` | 任意。supervisorツリーがシャットダウンされる前に呼ばれます |
| `application:stop/1` | app shutting down | `stop(State)` | `term()` | アプリの実行が終わった後、後片付けのために一度呼ばれます |
| Hot code update | SASL's release handler | `config_change(Changed::[{K,V}], New::[{K,V}], Removed::[K])` | `ok` | VMのrelup機能を使ったホットコードアップデートの後、設定値が変更されていれば呼ばれます |

### supervisor

| Trigger | Called By | Handled By | Return | 説明 |
|---|---|---|---|---|
| `supervisor:start_link/2-3` | parent process | `init(Arg)` | `ignore \| {ok, {SupFlag, [Child]}}` | supervisorを定義します。詳細は公式ドキュメントを参照してください |

### gen_server

| Trigger | Called By | Handled By | Return | 説明 |
|---|---|---|---|---|
| `gen_server:start_link/3-4` | supervisor | `init(Arg)` | `{ok, State [, Option]} \| ignore \| {stop, Reason}` | プロセスの初期状態を設定します |
| `gen_server:call/2-3` | client | `handle_call(Msg, From, State)` | `{Type::reply \| noreply, State [, Option]} \| {stop, Reason [, Reply], State}` | リクエスト/レスポンスのパターンです。メッセージを受信し、応答を返すことが期待されます |
| `gen_server:cast/2` | client | `handle_cast(Msg, State)` | `{noreply, State [, Option]} \| {stop, Reason, State}` | プロセスに送られる情報です。送りっぱなしで応答を待ちません |
| `Pid ! Msg` | client | `handle_info(Msg, State)` | same as `handle_cast/2` | 帯域外のメッセージです。監視シグナルや、exitを捕捉している場合の'EXIT'メッセージを含みます |
| Setting an `Option` value to `{continue, Val}` | the server itself | `handle_continue(Val, State)` | same as `handle_cast/2` | 長時間かかる処理を、内部で発火できるイベントに分割するために使います |
| `gen_server:stop/1,3` | client or supervisor | `terminate(Reason, State)` | `term()` | プロセスが自発的に、あるいはエラーによってシャットダウンするときに呼ばれます。プロセスがexitを捕捉していない場合、このコールバックは省略できます |
| `sys:get_status/2-3`, crash logs | client, the server itself | `format_status(normal \| terminate, [PDict, State])` | `[{data, [{"State", Term}]}]` | デバッグ呼び出しやエラーログに載る情報を追加・削除するために使います |
| N/A | supervisor | `code_change(OldVsn, State, Extra)` | `{ok, NewState}` | リリースを使ったホットコードアップグレードの際、適切な指示が与えられていれば、状態を持つプロセスを更新するために呼ばれます |

### gen_statem

#### プロセス管理

| Trigger | Called By | Handled By | Return | 説明 |
|---|---|---|---|---|
| `gen_statem:start_link/3-4` | supervisor | `init(Arg)` | `{ok, State, Data [, Actions]} \| ignore \| {stop, Reason}` | ステートマシンの初期状態とデータを設定します |
| N/A | internal | `callback_mode()` | `[state_functions \| handle_event_function [, state_enter]]` | FSMの種類と、状態への遷移時に特別な内部イベントを発火するかどうかを定義します |
| `gen_statem:stop/1,3` | client or supervisor | `terminate(Reason, State, Data)` | `term()` | プロセスが自発的に、あるいはエラーによってシャットダウンするときに呼ばれます。プロセスがexitを捕捉していない場合、このコールバックは省略できます |
| `sys:get_status/2-3`, crash logs | client, the server itself | `format_status(normal \| terminate, [PDict, State, Data])` | `[{data, [{"State", Term}]}]` | デバッグ呼び出しやエラーログに載る情報を追加・削除するために使います |
| N/A | supervisor | `code_change(OldVsn, State, Data, Extra)` | `{ok, NewState, NewData}` | リリースを使ったホットコードアップグレードの際、適切な指示が与えられていれば、状態を持つプロセスを更新するために呼ばれます |

#### 状態の処理と遷移

`callback_mode()`の値に応じて、`handle_event/4`または`StateName/3`のいずれかの関数で処理されます。関数のシグネチャは次のいずれかです。

- `handle_event(EventType, EventDetails, State, Data)`
- `State(EventType, EventDetails, Data)`

`callback_mode()`が`state_functions`を定義していても、`State`の値がリストでなければ`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`。その他のオプションについてはドキュメントを参照してください。

| Trigger | Called By | Event Type | Event Details | 説明 |
|---|---|---|---|---|
| `gen_statem:call/2-3` | client | `{call, From}` | `term()` | リクエスト/レスポンスのパターンです。メッセージを受信し、応答を返すことが期待されます |
| `gen_statem:cast/2` | client | `cast` | `term()` | プロセスに情報を送る必要があります。送りっぱなしで応答を待ちません |
| Pid ! Msg | client | `info` | `Msg` | 帯域外のメッセージです。監視メッセージや、捕捉された'EXIT'シグナルを含みます |
| `{timeout, T, Msg}` | `Action` return value | `timeout` | `Msg` | ステートマシンがTミリ秒の間新しいイベントを受信しなかったときに、内部で設定・受信できる特定のタイムアウトです |
| `{state_timeout, T, Msg}` | `Action` return value | `state_timeout` | `Msg` | ステートマシンがTミリ秒の間別の新しい状態に遷移しなかったときに、内部で設定・受信できる特定のタイムアウトです |
| `{next_event, internal, Msg}` | `Action` return value | `internal` | `Msg` | 外部からの呼び出しに見せずに自分自身を発火させたいステートマシンが生成できる内部メッセージです |

