更新: | 2021-07-20 |
---|---|
バージョン: | 2021.1 |
作者: | @voluntas |
URL: | https://voluntas.github.io/ |
この記事は 2013-06 あたりに書いたものを、少しずつ書き直しています。
知ってもらう事が中心です
- Erlang/OTP を知らない人にどんなものかを知って貰う
- Erlang/OTP がなぜ便利なのかを知って貰う
- Erlang/OTP がどこまで出来るのかを知って貰う
色々なところで使われている、歴史ある言語。 ただ、あくまでネットワークサーバを書くための言語なので汎用的な物では無い。 最近は HTTP サーバを書くという面から色々なところで意外に活躍していたりする。
- 安定したリリース
- 今のところリリースが遅れたことが無い
- 大量のテスト、ドキュメントの更新
- コミュニティが活発
- エリクソン主導
- 年 1 回 5 月頃にメジャーリリース
- 年 3 回のマイナーリリース
- 積極的な変更
- NIF (native implemented function) の採用
- マップ機能の採用
- JIT 対応
- コンテナ対応
- 「今」に合う変更を積極的に取り入れていく
- 採用事例
- LINE
- 任天堂 (ejabberd)
- Discord (Elixir)
- 文法がシンプル
- 覚えるだけならコストが低い
- 基本全部入り
- ネットワークサーバを書くならライブラリはほとんどそろってる
- 良くも悪くもエリクソンが開発している
- 多くの基幹で使われている
- エリクソンはかなりの人数で開発体制を整えている
- ガベージコレクションが軽量プロセス単位
- Java みたいに VM 単位で GC が走らない
- 元々はエリクソンのインターナル言語
- 電話交換機を作るための言語
- 1998 年にオープンソースになった
- ここ数年は開発速度が上がっている
- リポジトリが GitHub になった
- Pull-Request はベースになった
- Writing good commit messages
- rebar3 というビルドツールで圧倒的に便利になった
- erlang_ls という Language Server がでて圧倒的便利になった
- Erlang/OTP 25 が 2022 年 5 月にでる
- arm64 JIT 対応
- gen_udp_socket 対応
- 俗に言う関数言語のような文法
- 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]
- Open Telecom Platform
- 実は Erlang の本体はこの人
- サーバを開発するためのプラットフォーム
- コールバックベースの常駐プロセス
- application
- アプリケーションプロセス
- supervisor
- 監視プロセス
- gen_server
- サーバプロセス
- gen_event
- イベントプロセス
- gen_statem
- 有限状態機械プロセス
- 使いどころは難しい
- 常駐プロセス
- 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 も監視可能
- 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]} }.
TBD
TBD
TBD
url: | https://github.com/otp/rebar3 |
---|
- Erlang/OTP のビルドツール
$ ./rebar3 get-deps $ ./rebar3 update-deps
- 依存ライブラリの解決
- git / mercurial / svn など一般的なリポジトリに対応
$ ./rebar3 compile
- 自動でコンパイル
- コンパイルオプションも指定可能
{erl_opts, []}
$ ./rebar3 xref
- 相互参照解析ツール
- 存在しない関数を呼び出すとエラー
./rebar3 eunit
- 後ほど出てくる単体テストを実行する
- カバレッジも自動で生成
- Jenkins 向けのカバレッジレポートも rebar plugin を使えば生成可能
./rebar3 generate
- リリース機能
- パッケージング
- Erlang/OTP ランタイムも含む
./rebar generate-upgrade previous_release=<path> ./rebar generate-appups previous_release=<path>
- ホットアップデート
- VM が動作したままアップデート可能
- type/spec で型を定義する
- dialyzer で型解析を行いコードの矛盾点を探し出す
- 型定義ツール
指定された関数の型を想定する。あまり使われない ... 。
- 型解析ツール
-spec spam(binary()) -> binary().
binary 型を引数に取り、binary 型を戻す、もし integer() が返る可能性がある場合は問題の箇所を教えてくれる。
- 単体テスト
- 基本的な機能は備えている
- ただしテストツールとしてはまだまだ足りない部分が多い
- rebar を使えば気軽にカバレッジが取れる
- ランダムテスト
- もともとは Haskell で作られていた
- お金になるのは Erlang/OTP ということで切り換えた
- テストコードジェネレータの一種
- 型を定義してそこから乱数を生成させて、実装の矛盾を探し出す。
- 商用版は Erlang だけでなく C 言語までも対応している
- 状態を持つシステムに対してもテスト可能
- QuickCheck 考案者がテスト専門会社を立ち上げている
- http://www.quviq.com/index.html
- Volvo Cars chooses Quviq QuickCheck to certify embedded basic software