Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active October 12, 2021 22:34
Show Gist options
  • Save voluntas/7278561 to your computer and use it in GitHub Desktop.
Save voluntas/7278561 to your computer and use it in GitHub Desktop.
Erlang/OTP コトハジメ

Erlang/OTP コトハジメ

更新:2021-07-20
バージョン:2021.1
作者:@voluntas
URL:https://voluntas.github.io/

注意

この記事は 2013-06 あたりに書いたものを、少しずつ書き直しています。

目的

知ってもらう事が中心です

  • Erlang/OTP を知らない人にどんなものかを知って貰う
  • Erlang/OTP がなぜ便利なのかを知って貰う
  • Erlang/OTP がどこまで出来るのかを知って貰う

概要

色々なところで使われている、歴史ある言語。 ただ、あくまでネットワークサーバを書くための言語なので汎用的な物では無い。 最近は HTTP サーバを書くという面から色々なところで意外に活躍していたりする。

Erlang/OTP 実績

  • 安定したリリース
    • 今のところリリースが遅れたことが無い
    • 大量のテスト、ドキュメントの更新
    • コミュニティが活発
    • エリクソン主導
    • 年 1 回 5 月頃にメジャーリリース
    • 年 3 回のマイナーリリース
  • 積極的な変更
    • NIF (native implemented function) の採用
    • マップ機能の採用
    • JIT 対応
    • コンテナ対応
    • 「今」に合う変更を積極的に取り入れていく
  • 採用事例
    • WhatsApp
    • LINE
    • 任天堂 (ejabberd)
    • Discord (Elixir)

Erlang/OTP 魅力

  • 文法がシンプル
    • 覚えるだけならコストが低い
  • 基本全部入り
    • ネットワークサーバを書くならライブラリはほとんどそろってる
  • 良くも悪くもエリクソンが開発している
    • 多くの基幹で使われている
    • エリクソンはかなりの人数で開発体制を整えている
  • ガベージコレクションが軽量プロセス単位
    • Java みたいに VM 単位で GC が走らない

Erlang/OTP 歴史

Erlang/OTP 今後

  • Erlang/OTP 25 が 2022 年 5 月にでる
    • arm64 JIT 対応
    • gen_udp_socket 対応

Erlang 文法

  • 俗に言う関数言語のような文法
  • Prolog から大きな影響を受けている
  • 英語のような文法。or は ; で、and は , で、最後は . で終わる
  • 外から呼び出す場合は明示的に export する
    • その際は 引数の数 も明示的に指定する
    • 引数の数が違う場合は 別の関数 として扱われる
  • void 型は存在しない、すべての関数は何かしら値を返す
  • クラス、グローバル変数は存在しない、すべてそれぞれの関数で閉じる
-module(spam).

-export([main/0]).

main() ->
    io:format("~s\n", ["Hello, world."]),
    ok.

パターンマッチ

  • 基本はパターンマッチを使う、if は使わない
  • さらにガードを使う
  • 引数によるパターンマッチ
  • for は無い、再帰を使う
-spec eggs(string() | integer()) -> ok | error.
eggs(5) ->
    error;
eggs(N) when is_list(N) ->
    eggs(list_to_integer(N));
eggs(N) when N > 10 ->
    error;
eggs(_N) ->
    ok.
  • case .. of を使ったパターンマッチ
-spec eggs(string() | integer()) -> ok | error.
eggs(N) ->
    case N of
        5 ->
            error;
        N when is_list(N) ->
            eggs(list_to_integer(N);
        N when N > 10 ->
            error;
        _N ->
            ok
    end.
  • 特定の値のみパターンマッチで取得
1> {_, A, _} = {a,1,[b]}.
{a,1,[b]}
2> A.
1

バイナリパターンマッチ

  • bit 単位でパターンマッチが可能
  • Erlang 独特の文法
  • Python / OCaml / Lua などにも移植されるほど愛されている
    • python-bitstring
    • ocaml-bitstring
    • lua-bitstring
bacon(<<0:8, Rest/binary>>) ->
    ok;
bacon(<<A:7, B:1, _Rest/binary>>) ->
    case B of
        1 ->
            ok;
        0 ->
            error
    end.

無名関数

  • fun() -> ... end で気軽に関数生成
1> F = fun(N) -> N + 1 end.
#Fun<erl_eval.6.17052888>
2> F(10).
11

軽量プロセス

  • とにかく簡単に使える
  • 起動速度は 1 プロセス 1~2 マイクロ秒程度
  • spawn
-module(sample1).

-export([main/0]).

main() ->
    _Pid1 = spawn(fun() -> loop(20) end),
    _Pid2 = spawn(fun() -> loop(10) end),
    _Pid3 = spawn(fun() -> loop(5) end),
    ok.

loop(0) ->
    ok;
loop(Counter) ->
    Random = crypto:rand_uniform(1, 10),
    io:format("~p: ~p\n", [self(), Random]),
    timer:sleep(Random * 1000),
    loop(Counter - 1).

  • atom
    • Ruby で言うシンボル
  • binary
    • <<1,2,3>>
  • list
    • 文字列型は list 型と同一
  • tuple
    • {a,2,<<1,2>>, [3,4]}
1> [$a,$b].
"ab"
2> $a.
97
3> $b.
98

高階関数

  • lists モジュールをよく使う
  • lists:map/2
1> F = fun(N) -> N + 1 end.
#Fun<erl_eval.6.17052888>
2> lists:map(F, [1,2,3,4,5]).
[2,3,4,5,6]
  • lists:foldl/3
1> F = fun(N, Acc) -> Acc + N end.
#Fun<erl_eval.12.17052888>
2> lists:foldl(F, 0, [1,2,3,4,5]).
15

リストの処理

  • ヘッドとテール
  • [H|T]
1> [A,B|C] = [1,2,3,4].
[1,2,3,4]
2> A.
1
3> B.
2
4> C.
[3,4]

レコード

  • tuple のシンタックスシュガー
-module(spam).

-export([main/0]).

-record(kv, {key :: integer(),
             value :: any()}).

main() ->
    {key, 10, "20"} = K = #kv{key = 10, value = "20"},
    {key, 20, "20"} = K#kv{key = 20},
    ok.

内包表記

  • リストとバイナリ版が存在する
  • 意外に使うのが 混在 方式
1> [ {N*2, N} || N <- [1,2,3,4,5] ].
[{2,1},{4,2},{6,3},{8,4},{10,5}]
1> << <<(N*2):8>> || <<N:7,_:1>> <= <<1,2,3,4,5>> >>.
<<0,2,2,4,4>>
1> [ <<(N*2):8>> || <<N:7,_:1>> <= <<1,2,3,4,5>> ].
[<<0>>,<<2>>,<<2>>,<<4>>,<<4>>]

例外

  • try/catch が使える
  • 基本的にはプロセスが死んだらほっとくので気にしない
  • ログを取っておきたい時にキャッチ
1> 10 = 20.
** exception error: no match of right hand side value 20
2> catch 10 = 20.
{'EXIT',{{badmatch,20},[{erl_eval,expr,3,[]}]}}

メッセージパッシング

  • 軽量プロセスの間をメッセージでやりとりする
  • 参照渡しでは無くコピー渡
  • self() で自分のプロセス Id が取れる
  • メッセージをいくつか投げてみて、パターンマッチでメッセージを選択させる例
  • receive でブロックする
  • ! で送るとき自分の Pid も送れば双方向で通信が出来るようになる
  • 小さく送って大きい仕事をしてもらう
-module(sample2).

-export([main/0]).

main() ->
    Pid = spawn(fun loop/0),
    Pid ! <<1,2,3>>,
    Pid ! atom,
    Pid ! <<2,3,4>>,
    Pid ! [1,2,3],
    ok.

loop() ->
    receive
        <<1,2,3>> ->
            io:format("123!\n"),
            loop();
        <<2,3,4>> ->
            io:format("234!\n"),
            loop();
        Other ->
            io:format("~p\n", [Other]),
            loop()
    end.
> c(sample2).
> sample2:main().
123!
atom
ok
234!
[1,2,3]

OTP

  • Open Telecom Platform
  • 実は Erlang の本体はこの人
  • サーバを開発するためのプラットフォーム
  • コールバックベースの常駐プロセス
  • application
    • アプリケーションプロセス
  • supervisor
    • 監視プロセス
  • gen_server
    • サーバプロセス
  • gen_event
    • イベントプロセス
  • gen_statem
    • 有限状態機械プロセス
    • 使いどころは難しい

gen_server

  • 常駐プロセス
  • loop + spawn 自動でやってくれる
  • OTP の基本となる一つ
  • 決められたコールバックを設定することで対応可能
  • プロセスの状態を保持、管理出来る
  • プロセス自体はキューを持っている
  • init/1 は初期化
  • handle_cast/3 は非同期で処理
  • handle_call/2 は同期で処理
  • handle_info/2 はメッセージを受ける
  • terminate/2 は終了時処理
  • code_change はホットスワップ
-module(sample3).

-behaviour(gen_server).

-define(SERVER, ?MODULE).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

%% 処理が終わるまで待つ
send_sync(Binary) ->
    gen_server:call(?MODULE, {send, Binary}).

%% すぐに ok が返ってくる
send_async(Binary) ->
    gen_server:cast(?MODULE, {send, Binary}).

start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

init(Args) ->
    {ok, Args}.

handle_call({send, Binary} = _Request, _From, State) ->
    %% ここに処理を書く
    {reply, ok, State};
handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast({send, Binary} = _Request, State) ->
    %% ここに処理を書く
    {noreply, State};
handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

supervisor

  • 常駐プロセスの監視や再起動を行う
  • プロセスが死んだら勝手に起こしてくれる
    • 起こす条件や戦略も設定可能
  • 別の supervisor も監視可能
  • one_for_one
    • 一人が死んだらその人だけを再起動
  • one_for_all
    • 一人が死んだら他の人も道連れにして再起動
    • たとえば TCP Listen Socket を保持してるプロセスが死んだ場合とか
      • Accept ソケット関連を一気に殺す
  • 一つのプロセスがクラッシュした際、再起動する。ただし 10 秒の間に再起動が 5 回起きたらそのプロセスを停止させる
    • 無限にリスタートを繰り返させないため必要な手法
%% 戦略、再起動回数、秒数
{one_for_one, 5, 10}
  • sample3 という gen_server を supervisor の監視下に置く
    • sample3 がクラッシュしても自動再起動するが、10 秒の間に再起動が 5 回起きたらそのプロセスを停止させる
-module(sample_sup).

-export([start_link/0]).
-export([init/0]).

start_link() ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, []).

init([]) ->
    Sample3 = {sample3,
               {sample3, start_link, []},
               permanent, 5000, Type, [sample3]},
    {ok, { {one_for_one, 5, 10}, [Sample3]} }.

logger

TBD

counter

TBD

persitent_term

TBD

rebar

url:https://github.com/otp/rebar3
  • Erlang/OTP のビルドツール

deps

$ ./rebar3 get-deps
$ ./rebar3 update-deps
  • 依存ライブラリの解決
  • git / mercurial / svn など一般的なリポジトリに対応

compile

$ ./rebar3 compile
  • 自動でコンパイル
  • コンパイルオプションも指定可能
{erl_opts, []}

xref

$ ./rebar3 xref
  • 相互参照解析ツール
  • 存在しない関数を呼び出すとエラー

eunit

./rebar3 eunit
  • 後ほど出てくる単体テストを実行する
  • カバレッジも自動で生成
  • Jenkins 向けのカバレッジレポートも rebar plugin を使えば生成可能

generate

./rebar3 generate
  • リリース機能
  • パッケージング
  • Erlang/OTP ランタイムも含む

generate-upgrade

./rebar generate-upgrade previous_release=<path>
./rebar generate-appups previous_release=<path>
  • ホットアップデート
  • VM が動作したままアップデート可能

動的型解析

  • type/spec で型を定義する
  • dialyzer で型解析を行いコードの矛盾点を探し出す

typer

  • 型定義ツール

指定された関数の型を想定する。あまり使われない ... 。

dialyzer

  • 型解析ツール
-spec spam(binary()) -> binary().

binary 型を引数に取り、binary 型を戻す、もし integer() が返る可能性がある場合は問題の箇所を教えてくれる。

テスト

Eunit

  • 単体テスト
  • 基本的な機能は備えている
  • ただしテストツールとしてはまだまだ足りない部分が多い
  • rebar を使えば気軽にカバレッジが取れる

QuickCheck

  • ランダムテスト
  • もともとは Haskell で作られていた
  • お金になるのは Erlang/OTP ということで切り換えた
  • テストコードジェネレータの一種
  • 型を定義してそこから乱数を生成させて、実装の矛盾を探し出す。
  • 商用版は Erlang だけでなく C 言語までも対応している
  • 状態を持つシステムに対してもテスト可能
  • QuickCheck 考案者がテスト専門会社を立ち上げている
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment