7つの言語　7つの世界 ~第6章 Erlang ~3日目~


## はじめに

　[昨日](http://qiita.com/alancodvo/items/94bf1e565332e3349819)、[一昨日](http://qiita.com/alancodvo/items/da16f3ee36fea5c7fb2f)に引き続き、「7つの言語　7つの世界 -オーム社-」の第6章Erlangの3日目を勉強していく。

### 3日目本編


#### 並行性を実現するプリミティブ

　並行性を実現する基本プリミティブは、メッセージの送信(!を使用)、プロセスの生成(spawnを使用)、メッセージの受信(receive)の3つである。これら3つのプリミティブを用いて、メッセージの送受信を行い、それらを基本的なクライアント/サーバを実現するイディオムとしてまとめる。

##### 基本的な受信ループ

　まず、翻訳プロセスから始める。プロセスにスペイン語の文字列を送信すると、英語に翻訳された応答が返される。一般には、プロセスを生成し、その中でメッセージを受信して処理するループを書く。
　以下に基本的な受信ループを示す。

```Erlang:translate.erl
-module(transrate).
-export([loop/0]).

loop() ->
	recieve
		"casa" ->
			io:format("house~n"),
			loop();
			
		"blanca" ->
			io:format("white~n"),
			loop();
			
		_ ->
			io:format("I don't understand.~n"),
			loop()
end.
```

　この例では、本体の3箇所でloop()を呼び出していて、いずれも呼び出し元に戻っていない。しかし、Erlangでは末尾再帰が最適化されるため。loop()が各receive節の最後の操作であるかぎりオーバーヘッドはほとんど発生しない。つまりこの例では、永久ループさせているらしい(そんなことして大丈夫なのか)。
　receiveは、Erlangの他のパターンマッチング構文(caseや関数定義)と同じように動作する。receiveの後には、複数のパターンマッチング構文が続く。
　マッチング節のシンタックスはcase文と同じである。受信文字が文字列"casa"にマッチすると、houseという語を出力してから、loopを呼んでいる。

##### プロセスの生成

　まず、先ほど作成したモジュールをコンパイルする。

```Eralng
1> c(transrate).
{ok, transrete}
2> Pid = spawn(fun transrete:loop/0).
<0.38.0>
```

　プロセスを生成するにはspqwn関数を使用する。spawn関数は関数を引数にとる。引数に指定された関数は、新しい軽量プロセス内で開始される。spqwnはプロセスID(PID)を返す。
　返されたプロセスIDが<0.38.0>である。プロセスを名前で登録(変数に代入)しておけば、他のプロセスが、例えば共通のサービスを、プロセスIDではなく名前で見つけることができる。
　動的に変更できるようにしたいコード(ホットスワップ可能なコード)には、さらに別の形式が使える。リモートプロセスを生成する場合はspawn(Node, function)という形式を使用する。本書ではこれらのさまざまな形式についての説明は割愛されているので違う参考書を読んだ時にでも記録したい。

##### メッセージの送信

　Erlangに分散メッセージを渡すには!演算子を使用する(これはScalaと同じらしい)。この演算子の形式はPid!messageである。Pidは任意のプロセス識別子、messageには任意の値(プリミティブ、リスト、タプル)を指定できるらしい。実際にいくつかのメッセージを送信してみる。

```Erlang
3> Pid ! "casa".
"house"
"casa"
4> Pid ! "blanca".
"white"
"blanca"
5> Pid ! "loco".
"I don't understand."
"loco"
```

　行ことに1つのメッセージが送信される。receive節のio:formatがメッセージを出力し、コンソールが式の戻り値(つまり送信したメッセージ)を出力している。
　名前付きのリソースに分散メッセージを送信する場合は、node@server ! messageという形式を用いる。リモートサーバーのセットアップについても本書では割愛されているので後々調べたい。分散サーバーを簡単に稼働できるらしい。
　上の例では、基本プリミティブとそれらを組み合わせて基本的な非同期サービスを実現する方法について説明した。これは、どこにも戻り値がないものである。

#### 同期メッセージ

　このセクションでは、同期メッセージを送信する方法について説明する。戻り値を出力するだけの翻訳サービスを、本当に翻訳された文字列を返すサービスに変更してみる。
　メッセージングモデルを非同期モデルから同期モデルに変更するには次の3つの操作が必要になる。

* メッセージサービスの各receive節で、翻訳を要求するプロセスのIDと翻訳対象語のペアからなるタプルにマッチさせる。このIDを追加することで応答を返すことが出来る。
* 各receive節で、結果を出力する代わりに送信者に応答を返す。
* !プリミティブを使う代わりに、簡単な関数を書いて要求を送信し応答を待つ。

##### 同期受信

　まず、receive節にパラメータを追加する。タプルを用いたパターンマッチングを使用する。各receive節は次のようになる。

```Erlang
receive
	{Pid, "casa"} ->
		Pid ! "house",
		loop();
```

　任意の要素（これは常にプロセスIDになるはず）と、その後に続くcasaという語とマッチングを行う。マッチしたらhouseという語を受信側に返して、ループの先頭に戻る。
　このパターンでの注意点は、タプルの最初の要素は送信側のプロセスIDであることと、結果を出力するのではなく送信するという点である。

##### 同期送信

　受信側と対をなす送信側では、メッセージを送信して、即座に応答を待つ必要がある。ReceiverにプロセスIDが入ったとして、同期メッセージの送信は次のようになる。

```Erlang
Receiver ! "message_to_transrate",
	receive
		Message -> dosomething_with(Message)
	end
```

　メッセージは頻繁に送信するため、サーバーに対する要求をカプセル化するkとでサービスを単純化する。この例では、単純化された簡単なリモートプロシージャジャコールは次のようになる。

```Erlang
translate(To, Word) ->
	To ! (self(), Word),
	receive
		Translation -> Translation
	end.
```

　これで並行プログラミングに必要な要素は勉強できたのでこれらを組み合わせて使用してみる。

```Erlang:translate_service.erl
-module(translate_service).
-export([loop/0, translate/2]).

loop() ->
	receive
	{From, "casa"} ->
		From ! "house",
		loop();
	{From, "blanca"} ->
		From ! "white",
		loop();
	{From, _} ->
		From ! "I don't understand.",
		loop();
end.

translate(To, Word) ->
	To ! (self(), Word),
	receive
		Translation -> Translation
	end.
```

```Erlang
1> c(translate_service).
{ok, translate_service}
2> Translator = spawn(fun translate_service:loop/0).
<0.38.0>
3> translate_service:translate(Translator, "blanca").
"white"
4> translate_service:translate(Tramslator, "casa").
"house"
```

　単純にコンパイルし、ループを生成してから、作成したヘルパー関数を介して同期サービスを要求する。見ての通り、Translatorプロセスは指定した単語の翻訳結果を返すようになった。同期メッセージを受信するようになったわけである。
　以上が、きほｎん的なreceiveループの構造である。各プロセスにはメールボックスが用意されている。receive構文は、キューからメッセージを取り出して、特定の関数とマッチしたら、その関数を実行する。プロセスは、互いにメッセージパッシングによってやりとりが行われる。

#### プロセスのリンクによる信頼性の実現

##### リンクされたプロセスの生成

　このセクションでは、プロセスの動作原理を調べるために、簡単に終了する（死ぬ）プロセスを作成する。6連発のピストルを使用するロシアンルーレットをコーディングしてみる。発射するには、1~6までの数字をピストルプロセスに送信する。間違った数字（弾の入ったシリンダーの番号）を入力すると、プロセスは消滅する。

```Erlang:roulette.erl
-module(roulette).
-export([loop/0]).

% send a number, 1-6
loop() ->
	receive
		3 -> io:format("bang.~n"), exit({roulette,die,aat,erlang:time()});
		_ -> io;format("click=n"), loop()
	end.
```

　実装は思ったより簡単であった。メッセージループを作成し、3にマッチするとプロセスが消滅する。それ以外の番号は、メッセージを出力し、ループの先頭に戻る。
　これは簡単なクライアント/サーバ・プログラムである。クライアントはコンソール、サーバはrouletteプロセスである。
　このプログラムを実行してみる。

```Erlang
1> c(roulette).
{ok, roulette}
2> Gun = spawn(fun roulette:loop/0).
<0.38.0>
3> Gun ! 1.
"click"
1
4> Gun ! 3.
"bang"
3
5> Gun ! 4.
4
6> Gun ! 1.
1
7> erlang:is_process_alive(Gun).
false
```

　問題は、3を受信した時点でピストルプロセスが死ぬので、それ以上メッセージを受信しても何もおこらない点である。プロセスが生きているかどうか調べる方法が7行目である。
　7行目を実行するとプロセスが死んでいることが確認できる。このプログラムを改良し、プロセスが死んでいるかどうか確認する検死官(coroner)を書いてみる。

```Erlang:coroner.erl
-module(coroner).
-export([loop/0]).

loop() ->
	process_flag(trap_exit, true),
		receive
			{monitor, Process} ->
				link(Process),
				io:format("Monitoring process.~n"),
				loop();
			{'EXIT', From, Reason} ->
				io:format("The shooter ~p died with reason ~p.",
				[From, Reason]),io:format("Start another one.~n"),
				loop()
			end.
```

　いつものようにreceiveループを作成する。最初に、exitをトラップするプロセスとして、このプログラムを登録する必要がある。そうしないとEXITメッセージが受信されない。
　次に受信内容を処理する。receiveには2種類のタプルを指定する。monitorアトムで始まるタプルと、'EXIT'で始まるタプルである。それぞれについて詳しく見ていく。

```Erlang
{monitor, Process} ->
	link(Process),
	io:format("Monitoring process.~n"),
	loop();
```

　この部分は、検死官プロセスを、ProcessというPIDを持つ任意のプロセスにリンクする。spawn_linkを使用すると、リンクを設定した状態でプロセスを生成できる。監視対象のプロセスは、死ぬときにexitメッセージを検死官に送信する。では次に、エラーをトラップする部分に進む。

```Erlang
{'EXIT', From, Reason} ->
	io:format("The shooter died. Start another one.~n"),
	loop()
end.
```

　この部分は、exitメッセージにマッチする。'EXIT'の後にFrom(終了するプロセスから送られてきたPID), Reason(終了する理由)が続く3要素のタプルを使用する。マッチしたら、終了するプロセスのPIDと終了する理由を出力する。実行時は以下の流れで進む。

```Erlang
1> c(roulette).
{ok, roulette}
2> c(coroner).
{ok, coroner}
3> Revolver = spawn(fun roulette:loop/0).
<0.43.0>
4> Coroner = spawn(fun coroner:loop/0).
<0.45.0>
5> Coroner ! {monitor, Revolver}.
Monitoring process.
{monitor,<0.43.0>}
6> Revolver ! 1.
click
1
7> Revolver ! 3.
bang.
3
The shooter <0.43.0> died with reason {roulette,die,at,{8.48.1}. Start another one.
```

　今度は、単なるクライアント/サーバよりは高度になった。監視プロセスを追加したので、プロセスが死ぬとわかる。

##### 検死官から医者へ

　さらに改良してみる。ピストルを登録することで、ゲームプレーヤーはピストルのPIDを知る必要がなくなり、ピストルの生成を検死官に任せることができる。したがって、プロセスが死んだら検死官がプロセスを再起動することが可能になり、信頼性の向上と報告されるエラーの減少に繋がる。こうなると、検死官は医者へとグレードアップする。以下にそのコードを示す。

```Erlang:doctor.erl
-module(doctor).
-export([loop/0]).

loop() ->
	process_flag(trap_exit, true),
	receive
		new ->
			io:format("Creating and monitoring process.~n"),
			register(revolver, spawn_link(fun roulette:loop/0)),
			loop();
			
		{'EXIT", From, Reason} ->
			io:format("The shooter ~p died with reason ~p.", [From, Reason]),
			io:format("Restarting.~n"),
			self() ! new,
			loop()
		end.
```

　receiveブロックは、今度は2つのメッセージにマッチする。newとEXITの2つのタプルである。どちらも検死官プロセスの場合と若干異なり、次の1行が、newブロックのキモである。

```
register(revolverm spawn_lin(fun roulette:loop/0)),
```

　内側から順に説明する。まず、spawn_linkを用いてプロセスを生成している。この形式のspawnは、rouletteプロセスが死ぬといつでもdoctorに通知されるように2つのプロセスをリンクする。その後、PIDを登録しrevolverアトムに関連付ける。これでユーザは、revolver ! messageを用いて、このプロセスにメッセージを送信できるようになる。今度はPIDは必要ない。EXITをマッチングするブロックも賢くなっている。
　self() ! new,　により、自分自身にメッセージを送信し、新しいピストルの生成と登録を行っている。

```Erlang
1> c(doctor).
{ok, doctor}
2> Doc = spawn(fun doctor]loop/0).
<0.43.0>
4> revolver ! 1.
エラー
5> Doc ! new.
Creating and monitoring process.
new
6> revolver ! 1.
click
1
7> revolver !3.
bang.
3
The shooter <0.47.0> died with reason {roulette,die,at,{8,53,40}}.
	Restarting.
Creating and monitoring process.
8> revolver ! 4.
click
4
```

　4行目でエラーになるのは、プロセスをまだ生成していなかったからである。
　上記の流れで、Doctor経由でrevolverを作成するという手順が追加されているのがわかる。ピストルとやりとりするには、GunPIDではなく、revolverアトムを介してメッセージを送信する。

### 3日目で学んだこと

　今日で、Erlangを使用すれば堅牢な並行システムを簡単に作れることがわかった。エラー処理ではなく、何かがクラッシュしたら、単純に再起動するだけというわかりやすいものであった。互いを監視するモニタも簡単に作れるらしい。
　基本ライブラリには、今日紹介した以外にも、障害時には自動的に再起動されるモニタサービスや、キープアライブ昨日を構築するためのツールが豊富に用意されているそうなのでそれを眺めても面白いかもしれない。
　とりあえずざっとErlangの勉強ができたから後々何かに使いたいところである。