thinking in erlang (japanese version)

37
Thinking in Erlang 手手手手手手手手手手手手手手手手手手手手手手手手手手手手手手手 Robert Baruch [email protected] Version 0.9.1 February 5, 2007 Copyright This work is licensed under the Creative Commons Attribution-Share Alike 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.

Upload: ether

Post on 06-Jun-2015

4.519 views

Category:

Documents


0 download

DESCRIPTION

"Thinking in Erlang" translated into Japanese

TRANSCRIPT

Page 1: Thinking in Erlang (Japanese version)

Thinking in Erlang

手続き型プログラミング経験者のための関数

型プログラミングガイド

Robert Baruch

[email protected]

Version 0.9.1

February 5, 2007

Copyright

This work is licensed under the Creative Commons Attribution-Share Alike 2.5

License. To view a copy of this license, visit http://creativecommons.org/licenses/by-

sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San

Francisco, California, 94105, USA.

Page 2: Thinking in Erlang (Japanese version)

1 導入............................................................................................................4

1.1 この文書が扱わないこと........................................................................4

1.2 この文書の内容...................................................................................4

1.3 コードのErlangへの移植......................................................................4

1.4 Hello,World....................................................................................5

1.5 hello.erlのコンパイルと実行.................................................................6

2 変数のスコープ..........................................................................................7

2.1 状態がないということ...........................................................................7

2.2 状態なしに対処する..............................................................................8

3 パターンマッチ..........................................................................................9

3.1 パターンマッチの基本...........................................................................9

3.2 関数の引数でのパターンマッチ..............................................................10

3.3 ifと case.......................................................................................11

3.4 ガード条件......................................................................................12

4 繰り返し...................................................................................................14

4.1 再帰..............................................................................................14

4.2 より手軽なループ..............................................................................16

4.3 手軽なループのまとめ.........................................................................18

5 プロセス...................................................................................................19

5.1 2つのことを一度に行う.......................................................................19

Page 3: Thinking in Erlang (Japanese version)

5.2 プロセス間通信.................................................................................20

5.3 プロセスが停止する条件......................................................................21

5.4 オブジェクトとしてのプロセス..............................................................23

5.5 汎用サーバ......................................................................................25

5.6 分散Erlang....................................................................................26

5.7 同期化は不要....................................................................................26

6 エラー処理...............................................................................................27

6.1 「失敗するに任せよ」.........................................................................27

6.2 作業プロセスと監督プロセス..............................................................................28

Page 4: Thinking in Erlang (Japanese version)

1 導入

1.1 この文書が扱わないこと

もし読者がこの文書からプログラミング自体を学ぼうと期待しているのであれば、き

っと失望させてしまうだろう。読者は C++、C#、Javaのいずれかをある程度よく知ってい

ることが期待されている。同様に、この文書は Erlangのリファレンスではない。Erlangの

ドキュメントはすでに多く存在しており、それらと重複するような情報をここに詰め込むこ

とには意味がないからだ。

さらに、我々は他の特定の言語(関数型であれ何であれ)の代わりに Erlangを使うと

いう方がよいということを言おうとしているわけでもない。読者は Erlangで何かをしたいと

思ったからこそこの文書を読んでいるのであり、既に Erlangを使おうと(少なくとも試して

みようと)決めているのだということを想定している。

最後に、ここには Erlangのインストール方法は書かれていない。繰り返しになるが、

特定の環境に Erlangをインストールするための情報はウェブ上に多く存在している。

1.2 この文書の内容

Erlangは関数型プログラミング言語である。これは C++、C#、Javaのような手続き

型プログラミング言語とは非常に異なったものだ。手続き型言語が力点を置いているのはデ

ータの構造だとか何らかのグローバルな状態を操作する一連の命令とかいったものだ。これ

に対して関数型言語はプログラムというものをグローバルな状態を持たない関数を評価する

ことだという風に捉える。

熟練した手続き型ソフトウェアのアーキテクトは関数型プログラミング言語にグロー

バルな状態がないのをみて奇妙に感じるかもしれない。この文書は Javaから Erlangへと概

Page 5: Thinking in Erlang (Japanese version)

念を結びつけることにより理解を容易にしようとしている。筆者は Javaのベテランだが、長

い間 C++を放棄し、C#についてはこれまで移行する必要に駆られなかった。そういうわけ

でアナロジーはほとんどの場合 Javaからのものである。

1.3 コードのErlangへの移植

手続き型言語から関数型言語へとコードを移植するのは容易なことではない。そのた

めにはそのコードが何を成し遂げようとしているのかを理解する必要がある。両者のパラダ

イムが異なるため、一から新しいコードをデザインしなおさなければならなくなってしまう

だろう。これこそが Erlangでどう考えるかを知ることが重要な理由なのである。

1.4 Hello, World

次にこれがくるということは予想していただろうから、まずはこれから片付けよう。

1 -module(hello).

2 -export([hello/0]).

3

4 hello() ->

5 io:fwrite("Hello, World!~n", []).

図 1 Hello, World

とても変てこに見えるが、これを既に知っているものと結びつけてみよう。1行目は

モジュールの宣言で、これはクラスの宣言のようなものだ。これはこのファイル(あるいは

モジュール)が helloという名前であるということを言っている。C++や Javaとは違ってモ

ジュール名は小文字で始まらなければいけないことに注意してほしい。これは大文字で始ま

る語は Erlangでは変数にしか使わないことになっているからだ。

2行目はどの関数がモジュールの外から呼ばれてもいいのかを示している。ところで

関数というのは Erlangではメソッドのことをそう呼ぶのだ。つまり exportによって public

メソッドと privateメソッドの区別のようなことができる。Erlangには継承がないので

protectedメソッドに相当するものはない。

Page 6: Thinking in Erlang (Japanese version)

exportステートメントの中の大括弧はリストを示している。Erlangのリストは配列の

ようなものだが、伸縮させたり要素を挿入したり削除したりもできる。後でもっと複雑な例

が出てくるが、今のところはこのリストは1つの要素のみを持っているのだという認識に留

めておいてほしい。

hello/0は名前が helloでアリティが 0であるような関数をエクスポートするというこ

とを示している。アリティとは単に関数が取る引数の数のことだ。同名でアリティの異なる

関数が 2つあっても構わないが、同名でアリティも同じであるような関数が 2つある場合は

実のところ同じ関数だ。これについても後で見ていく。

4行目は関数の宣言だ。この矢印は関数の本体の開始を指し示していて、本体はピリオ

ドで終わる。

5行目がその本体だ。ここでは ioモジュールの fwrite関数を呼んでいる。io:fwriteは

Cでいう printfのようなもので Java(1.5 以上)では PrintStream.printfにあたる。最初の引

数が書式化文字列で、2つ目の引数が書式化文字列の使用する物のリストだ。ここで空の大括

弧はそのリストが空であることを示している。

チルダ文字(~)は Cの printf や Javaの PrintStream.printfにおける%文字に相当す

る。~nは環境依存の改行を出力するための書式指定だ。といっても現実には行送り(ASCII

の 0x0a)だけが出力されるのでWindows環境(恐らく LF や CRの代わりに NELを使う

IBM OS/390 システムも)は十分にサポートされていない。

1.5 hello.erlのコンパイルと実行

では Erlangのインタラクティブシェル上で hello.erlのコンパイルと実行に取り掛か

ろう。

Page 7: Thinking in Erlang (Japanese version)

ekmac:~ ek$ erl

Erlang (BEAM) emulator version 5.5.2 [source] [async-threads:0] [hipe]

[kernel-poll:false]

Eshell V5.5.2 (abort with ^G)

1> c("hello.erl").

{ok,hello}

2> hello:hello().

Hello, World!

ok

3> init:stop().

ok

4> ekmac:~ ek$

図 2 hello.erlの実行

最初のコマンドがカレントディレクトリの hello.erlをコンパイルしている。ここには

絶対パスや相対パスを書くこともできる。このコマンドは関数の形式を取っていてピリオド

で終わるということに注意してほしい。

次に helloモジュールの中の hello/0関数を呼んでいる。これはクラスのスタティック

メソッドを呼ぶのによく似ている。ここでも呼び出しはピリオドで終わる。最後の”ok”は単

に hello/0の戻り値だ。関数は最後の文の戻り値を返す。この場合それは io:fwrite/2の戻り

値で、この関数は okという値を返す。[訳注:この okはアトムという型の値である。3.3を参

照]

最後にシェルを抜けるために init:stop/0関数を呼び出している。

Page 8: Thinking in Erlang (Japanese version)

2 変数のスコープ

2.1 状態がないということ

先ほど簡単に述べたように、Erlangにはグローバルな状態というものがない。これは

グローバル変数がないということを意味している。モジュールはクラスのようなものだとも

述べたが、クラスが属性を持つことができるのに対してモジュールは変数を持つことができ

ない。

Erlangの変数のスコープはそれが宣言された関数の中に限定される。さらに変数は一

度定義されたら再度代入を行うことはできない。これが意味するところは、関数は状態を持

っていて、なおかつ持っていないということだ。

最後に、Erlangの変数は特定の型を持つものとして宣言することはできない。型は実

行時に決定される。これはある型を他の型と混同することはありえず、同一スコープで変数

を再度型付けするようなこともありえないためだ。以下が変数の用例だ。

Page 9: Thinking in Erlang (Japanese version)

Eshell V5.5.2 (abort with ^G)

1> X = 1.

1

2> X.

1

3> X = 2.

=ERROR REPORT==== 17-Jan-2007::19:58:46 ===

Error in process <0.30.0> with exit value: {{badmatch,2},

[{erl_eval,expr,3}]}

** exited: {{badmatch,2},[{erl_eval,expr,3}]} **

4> F = 1.0.

1.00000

5> S = "abc".

"abc"

6> L = [1, 2, 3].

[1,2,3]

7> L2 = [$a, $b, $c].

"abc"

図 3 様々な変数

変数が値を割り当てられたとき、それは束縛されたという風に言う。一度変数が束縛さ

れると再度束縛されることはできない。某アメリカ大統領風にいうと「私を一度束縛したも

のは…ええと…やるじゃないか。束縛してみろ…二度と束縛されないぞ!」ということだ。

[訳注:諺を間違えていったジョージ・ブッシュのスピーチに由来]

2行目は変数 Xの値を読み出している。3行目では同一スコープの変数を再束縛してい

るためにエラーになっている。このエラーは badmatchというものだが、これについては後

ほど説明する。

4行目は浮動小数点数、5行目は文字列、6行目はリスト、7行目は文字列のリストを

示している。($xは C や Javaでの単一引用符で括る ’x’ に相当し、$\nが改行文字、$$が

$記号になる)

L2が文字列を表示していることに注意してほしい。本当は文字列型というものは

Erlangには存在しないのだ。文字列は文字のリストとして実装されている。実を言うと文字

型というものも存在せず、文字列は単なる整数のリストである。そうすると 16 ビット整数の

Page 10: Thinking in Erlang (Japanese version)

リストを作ってそれを Unicode文字列と呼ぶこともできそうだが、Erlangは Unicodeをサ

ポートしていない。Erlangで Unicodeを扱うことについてはこの文書のスコープの範囲外

だ。Unicode文字列を表現すること自体は簡単なことだが、2つの Unicode文字列を適切に

正規化して比較することや、ある Unicode文字が空白文字であるかどうかを判別することさ

え実装されていないということを述べておけば十分だろう。

2.2 状態なしに対処する

ここまでの話で頭がパンクしてしまったかもしれない。読者の Cプログラムや Javaプ

ログラムでは普通 i++を少なくとも 1 回は使っているに違いない。しかしそれは変数の値を

変更するということになるので Erlangでは使うことはできないのだ。

ここで Erlang 流に考える(あるいは関数型風に考える)ということが出てくる。 i++

はさておいて、S/=1000を見てみよう。おそらく Sはミリ秒単位で、それを秒単位に変換し

たいのかもしれない。我々は Erlangを使っているのでこれは S2=S/1000のように実装しな

ければならないだろう。

ここで暴動が巻き起こる。「メモリ使用量を倍にしているだけじゃないか!」誰かが

喚く。「何てもったいない!」別の者が叫ぶ。人々はパトカーをひっくり返し「Erlangは使

えない!」と合唱する。

さて、どうして S2を割り当てた後でも Sを保持しておくなんて言うのだろうか。Sと

S2と両方必要だから? もしそうなら両方保持していく以外に選択の余地はない。もし Sが

必要でなければ S2を割り当てた後に捨ててしまっても構わない。もしくはもし S2が一度し

か必要でないなら S2さえ取り去って必要なところで一時変数を使うようにしてもいいだろ

う。さらにひとたび Sが定義された関数が終了したなら最早 Sも必要ではない。

ポイントは再代入を除去することによってコンパイラが最適化処理を簡単に行うこと

ができるようになるということなのだ。

勿論 Erlang シェルでこれをやった場合は、シェルは次にユーザが何を行うか知らない

ので、Sを保持しておくという選択しかない。

Page 11: Thinking in Erlang (Japanese version)

3 パターンマッチ

3.1 パターンマッチの基本

3要素のタプルを考えよう。タプルというのはリストのようなもので、リストとは違

って通常伸縮せず、リストのような tail(これについては後で述べる)を持たない。Erlang

ではタプルを中括弧で表し、{ 1, 2, "burger" }だと 3要素のタプルとなる。最初の 2つが整

数で、3つ目がリストだ[訳注:文字列は整数のリストなので]。

さてこのタプルがmiddle/1という関数に渡されたとしよう。この関数はこのタプルを

変数にとるので、middle(Arg)となる。関数は 2 番目の要素を返すようにしたい。

方法としては erlang:element/2を使うこともできる。これはどんな大きさのタプル

についても使うことができて N 番目の要素を返すものだ。しかし、もしタプルが 3要素から

成ることがわかっているのであればもっと簡単な方法がある。

1 middle(Arg) ->

2 { _First, Second, _Third} = Arg,

3 Second.

図 4 タプルのマッチング

2行目の代入がパターンマッチだ。まず最初にもし Argが 3要素のタプルでなかった

場合はパターンマッチが badmatchエラーで失敗し、この関数は失敗する。しかし我々は

middle/1には 3 項のタプルしか渡さないつもりなのでこれは OKだ(そのつもりじゃないと

いう場合は後のほうを読んでほしい)。

2 番目に、タプルの各要素は左辺に対してマッチングされ、かつ束縛される。アンダー

スコアで始まる変数も束縛されるが、これはその値が当該スコープで実際に使用されること

がないことを暗に示している。もっと簡単に{ _, Second, _}と書くこともできる。これは束

Page 12: Thinking in Erlang (Japanese version)

縛されることのない特殊変数 _ を使っているが、文脈によっては先ほどの書き方より意味が

明確ではないかもしれない。

したがって、{ 1, 2, "burger" }が与えられた場合パターンマッチは Secondに 2を割

り当て、他の要素については無視する。そして関数は Secondを返す。

先に述べたように、もしmiddle/1がタプル以外のものや 3要素でないタプルを渡され

た場合、middle/1は badmatchエラーで失敗する。

2> test:middle( { 1, 2, "burger" } ).

2

3> test:middle( "burger" ).

=ERROR REPORT==== 18-Jan-2007::19:08:39 ===

Error in process <0.30.0> with exit value: {{badmatch,"burger"},

[{test,middle,1},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {{badmatch,"burger"},

[{test,middle,1},{shell,exprs,6},{shell,eval_loop,3}]} **

図 5マッチングの失敗

badmatchエラー情報はユーザが”burger”を何かに対してマッチングさせようとして

test:midlle/1(これは shell:exprs/6 から呼ばれていて、 shell:exprs/6 はさらに

shell:eval_loop/3から呼ばれている)で失敗したということを伝えている。

任意の大きさのタプル内の 2 番目の要素にマッチングさせるには erlang:element/2

を使うしかないのだが、リストであればどんな大きさのものでもマッチングさせることがで

きる。以下がリストのパターンマッチングの例だ。

1 middle( [ _First, Second |_Tail] ) ->

2 Second.

図 6 リストのマッチング

1行目の構文は引数が 2つの要素を持っていてその後にリストの残りの部分が続くとい

うことを示している。リストの残りの部分というのはリストのことなので、 [ Head | Tail ]

を [ 1, 2, 3, 4, 5 ]にマッチングさせるとHeadに 1、Tailに[ 2, 3, 4, 5 ]がセットされる。も

しリストが tailを持たない場合 tailには空リストがセットされる。これはつまり[ Head | Tail

] を [ 1 ]にマッチングさせると Headに 1、Tailに[ ]がセットされるということだ。[ Head

Page 13: Thinking in Erlang (Japanese version)

| Tail ]を[]にマッチングさせようとすると失敗する。これは Headが要素を必要とするのにリ

スト中に要素が存在しないためだ。

ここで 1行目は少なくとも 2要素のリストを要求しているということがとわかるだろ

う。もしリストが 2 要素よりも小さかった場合、 _First と Second はマッチングでき

ず、badmatchエラーで終わることとなる。

3.2 関数の引数でのパターンマッチ

Erlangは実行すべき関数を決定しようとするときに関数の名前だけでなく呼び出し元

が与えた引数にもマッチするものを探そうとする。次に挙げる例はタプルのマッチングを引

数のパターンマッチを使ってよりコンパクトなやり方で表現している。

1 middle( { _First, Second, _Third} ) ->

2 Second.

図 7 関数の引数におけるマッチング

最後の 2つの例(タプルの 2 番目を抽出するものとリストの 2 番目を抽出するも

の)を纏め上げて同じ関数の複数の候補として定義することもできる。

1 middle( { _First, Second, _Third} ) ->

2 Second;

3 middle( [ _First, Second, |_Tail ] ) ->

4 Second.

図 8 関数の候補とマッチング

1行目と 3行目で 2つのmiddle/1関数を定義している。個々の関数呼び出しについて

Erlangは当該関数の候補を走査し、その候補を引数とマッチさせようとする。ここでは 2つ

の候補が存在するのが分かる。最初のものは引数が 3要素のタプルであった場合のみマッチ

する。2 番目のものは 2要素以上のリストについてマッチする。

候補は適切な順序で並べておくことが重要だ。Erlangは最初にマッチするものが見つ

かったところで検索を止めるので、もっとも条件が限定的なものからもっとも条件の緩いも

のへと並ぶようにしよう。

Page 14: Thinking in Erlang (Japanese version)

最初の候補がピリオドでなくセミコロンで終わっていることに注意しよう。これはコ

ンパイラに別の候補が次に来るということを教えてやるためのものだ。2行目にピリオドを

使った場合、コンパイラは 3行目がmiddle/1の再定義であるとみなしてエラーになってしま

う。

3.3 ifと case

C や Javaと同様に if文と switch文も存在する。Erlangではこれらは ifと caseと呼

ばれる。ifは真となるような選択肢の本体、caseはマッチした選択肢の本体を実行してその

結果の値を返す。以下がその例だ。

1 is_even(X) ->

2 if

3 X rem 2 == 0 ->

4 true;

5 true ->

6 false

7 end.

図 9 ifの例

if文の中には好きなだけ節を書いてよい。Erlangは各節をチェックし、真と評価され

た最初のものを評価して返す。if文は値を返す文であるため、その結果を変数に割り当てて関

数の後の方で利用するというようなこともできる(i.e. Ret = if...end.)。

case文はパターンに対してマッチする。

Page 15: Thinking in Erlang (Japanese version)

1 many(X) ->

2 case X of

3 [ ] ->

4 none;

5 [ _One ] ->

6 one;

7 [ _One, _Two ] ->

8 two;

9 [ _One, _Two, _Three |_Tail ] ->

10 many

11 end.

図 10 caseの例

ここでは Xをパターンに対してマッチングしている。マッチした最初のパターンの本

体が評価されて返される。ここでの戻り値は二重引用符で括られていないので文字列ではな

い。また大文字で始まってもいないので変数でもない。これはアトムといって、大域的な名

前空間を持つ列挙値である[訳注:Lisp や Rubyのユーザはシンボルに相当すると思えばいいか

もしれない]。アトムはどこでも作ることができ、受け渡ししたりマッチさせたりすることが

できる。アトムをその名前文字列に変換するには erlang:atom_to_list/1を使う。

多くの場合アトムは文字列よりも効率的であり、また整数を使うよりも意図が分かり

やすい。だから文字列や整数を列挙値として使おうとするような場面ではアトムを使えない

か検討してみてほしい。

アトムは単一引用符を使って書くこともできる(noneの代わりに'none’)。これによ

り大文字で始まるアトムや’.’のような記号のアトムを作ることもできる。この場合アトム

は”.”という名前である。まあこの辺は余談だが。

もし 9行目が何故[ _One, _Two | _Tail ]に対するマッチではないのかと疑問に思った

ら前のセクションの終わりにあるリストの tailのマッチングルールを再読してほしい。

Ifと caseの構文を理解することは重要である。本体の後に別の候補が続く場合、その

本体はセミコロンで終わらなければならない。そして最後のものは必ず endトークンで終わ

る。もし ifないし caseの後に別のステートメントが連続する場合は endの後にコンマを続

けなければならない。もし ifないし caseが関数内で最後の文である場合はピリオド(その関

数の別の候補が後に続く場合はセミコロン)で終わらなければならない。

Page 16: Thinking in Erlang (Japanese version)

つまり一般化して言うとセミコロンは別の候補が次にくるということを示していて、

ピリオド(if や caseの場合は end)はこれで終わりだということを示している。これを覚え

ておけば Erlang 流に考えられるようになるだろう。

3.4 ガード条件

関数や case文はそれらが実行されるべきかどうかについて追加のチェックを行うこと

ができる。これはガードシーケンスと呼ばれるもので、関数を呼んでいいかどうかの追加条

件付きの真偽式である(ただし Erlangマニュアルに記載のあるごく少数の関数には適用不

能)。

一例として、ガード条件を使って is_even関数をこう書きなおすことができる。

1 is_even(X) when X rem 2 == 0 ->

2 true;

3 is_even(_X) ->

4 false.

図 11 ガードシーケンスの例

ガードシーケンスはパターンマッチングと組み合わせて使うと一層便利なものにな

る。あるパターンが最低 1要素のリストにマッチするが、その要素が偶数かどうかによって

はマッチできないとしよう。パターンと一緒にガード条件を使うことによってこうしたこと

が可能になるのである。

ガードシーケンスはとても複雑なものにすることもできる。上記のようなガードシー

ケンスは単一のガード式から成っているが、一般にガードシーケンスは一連のガード条件を

セミコロンで区切って並べたもので、そのいずれかが真でなければならない。個々のガード

条件はガード式をコンマで区切って並べたものでその全てが真でなければならない。Erlang

のマニュアルはガードシーケンスの構文についてもっと詳細に取り扱っている。

Page 17: Thinking in Erlang (Japanese version)

4 繰り返し

4.1 再帰

よろしい、i++がないとしたら Erlangではどうやって繰り返しを書くのだろうか?

短く答えるとこうだ。再帰を使う。

…また暴動を引き起こしてしまった。

ちょうど前のセクションで再代入がないということが大した問題ではないと述べたの

と同様に、再帰にしてもその再帰が末尾再帰である限りは問題ないのだ。

末尾再帰とは関数が自分自身を呼び出していて、かつその呼び出しが最後の処理となっ

ているようなもののことを意味する。もしある関数の最後の処理が自分自身の(または何で

あれ他の関数の)呼び出しである場合、その最後の関数呼び出しに対する引数をセットアッ

プし次第、当該関数スコープ内で定義された変数(その関数の引数も含む)は除去して構わな

くなる。だから末尾再帰を使う場合には関数呼び出しはメモリを追加で使わなくて済むので

ある。

繰り返しを書きたい場合、繰り返しを行う関数にループ変数を含めなければならな

い。これは一般に繰り返しを行う専門の関数を定義するのが賢いやり方だということを意味

する(何かをして、ループをして、また何か他のことをやるという関数ではなく)。

次に挙げるのは末尾再帰を使って 1から 10までの整数を表示する例である。ここでは

引数のマッチングを使っているが、シーケンシングというまだ紹介していない構文も使って

いる。

Page 18: Thinking in Erlang (Japanese version)

1 -module(count).

2 -export([go/0]).

3

4 go() ->

5 go(1).

6

7 go(11) ->

8 io:fwrite("~n", []);

9 go(X) ->

10 io:fwrite("~.10B ", [X]),

11 go(X+1).

図 12 カウンタの例

このモジュールは go/0と go/1という 2つの関数を定義している。この 2つはまった

く別の独立した関数だ。両者は互いに何の関係もない。go/0は単に 1を引数にして go/1を呼

び出し、ここがループの開始となる。

10行目では io:fwrite文がピリオドではなくコンマで終わっている。文の間にコンマ

を入れると文は順次実行される。またここでは関係がないが、順次実行された文のブロック

の戻り値は最後の文の戻り値となる。これはC や Javaと同じだ。

この辺で~.10Bが何を意味するかを知るために Erlangのリファレンスに親しんでお

くとよいだろう。ヒント:モジュールのページをチェックして ioモジュールを探すこと。あな

たがもっと探しやすいAPIドキュメントを作ってくれるなら大歓迎だ。

go/1の最後の処理が go/1の再帰呼び出しであるということに注意してほしい。これ

は go/1が適切に末尾再帰を実装しているということを意味している。11行目の実行中に

X+1がセットアップされ、局所変数 Xは破棄することができるようになり、go/1がまた呼び

出される。これは実装上呼び出しの形を取る必要はなく、gotoであってもよい。したがって

末尾再帰の呼び出しではスタックが余計に使われることはない。

一方で、もし 10行目と 11行目をひっくり返して go(X+1)の後に Xの表示がくるよう

にしたとする(したがって数字は逆順に表示される)と、go/1はもはや末尾再帰ではなくな

り、スタック上に引数と呼び出し結果を保持しておかなければならなくなるだろう。だから

関数は末尾再帰になるように心がけよう。

Page 19: Thinking in Erlang (Japanese version)

以下は非末尾再帰な関数の典型例だ。

1 -module(badFactorial).

2 -export([factorial/1]).

3

4 factorial(0) -> 1.

5 factorial(N) ->

6 N * factorial(N-1).

図 13 よくない再帰

factorial/1の最後の処理が factorial/1の呼び出しであるように見えるかも知れない

が、そうではない。実際には factorial(N)は factorial(N-1)が返ってきた後にその結果に Nを

掛けてから戻らなければならない。この関数は末尾再帰ではないのでメモリを無駄遣いする

ことになる。

再帰しているのに末尾再帰ではないような関数でも、結果を総計するようなアキュム

レータを引数に追加することによって「治療」することができる場合がある。以下がアキュ

ムレータを使って末尾再帰にした階乗の例だ。

1 -module(goodFactorial).

2 -export([factorial/1]).

3

4 factorial(N) ->

5 factorial(N, 1).

6

7 factorial(0, Acc) ->

8 Acc,

9 factorial(N, Acc) ->

10 factorial(N-1, N*Acc).

図 14 適切な末尾再帰

ここには 3つ関数がある。これらを「セットアップ関数」、「終了関数」、「再帰関

数」と呼ぶことにしたい。セットアップ関数はエクスポートしているが他の 2つはプライベ

ートにしている。

Page 20: Thinking in Erlang (Japanese version)

この factorial/2は末尾再帰になっていることが分かるだろう。一度引数を計算したら

残る処理はその引数を使って factorial/2を呼び出し、何であれその結果を返すということだ

けだ。また factorial/2がその時点までの計算結果を総計するアキュムレータを持ち歩いてい

るということも分かる。Nからカウントダウンしていって、その都度アキュムレータ(セッ

トアップ関数の中で 1に初期化)に Nをかけていき、Nが 0になったところで何もすること

がなくなって終了し(終了関数)、あとは単純にそこまでの総計を返すのだ。

引数としてのアキュムレータの概念は最初のうち理解しにくいかもしれないが、自分

でいくつか関数を書いて試してみると Erlang 流の考え方を習得することができるだろう。

もしNが負の数だったらどうなるだろうか。無限ループに陥ってしまう。これを防ぐ

にはガード条件を使うが、これについては後で触れる。

ある種の再帰的な問題には末尾再帰は不適切だったり、末尾再帰にする価値がなかった

りすることがある。たとえば左右木のトラバーサルは再帰的に書くことができる。しかし各

ノードについて左ノードをトラバースしたら次に右ノードをトラバースするということをし

なければならない。左ノードのトラバースがトラバーサルの最後の処理ということにはなら

ないため、このトラバーサルは末尾再帰にすることはできない。

ツリートラバーサルを末尾再帰にすることは可能かもしれないが、そのコードと考え

方は奇妙で追いにくいものになるかもしれない。筆者は奇妙でねじくれたコードからは距離

を置くように薦める。もし目的が必要のないところで効率を出したり、メンテナンスが悪夢

になるようにしたり、自分がいかに賢いかを証明するためとかだったら話は別だ。だが、そ

ういう人は多分全くプログラミングをすべきではないだろう。

4.2 より手軽なループ

リストに対して繰り返しを行うとき、3つの効率的なリスト関数を活用することができ

る。その 3つとはmap、foreach、foldだ。これらの関数を使うことでループ関数やアキュ

ムレータを作らなくても済むことがある。

map関数は単純に各要素が与えられたリストの対応要素に与えられた関数を適用した

結果であるようなリストを返す。たとえば、3要素タプルのリストがあったとして、各タプ

ルの 2 番目の要素を取り出すには以下のようにする。

Page 21: Thinking in Erlang (Japanese version)

1 -module(mapping).

2 -export([extract/1]).

3

4 extract(List) ->

5 lists:map(fun extractFromTuple/1, List).

6

7 extractFromTable( {_, Second, _} ) ->

8 Second.

図 15 mapの例

26> c("mapping.erl").

{ok,mapping}

27> mapping:extract( [ {1, 2, 3}, {4, 5, 6} ] ).

[2,5]

図 16 mapの例の使い方

もしmap関数の引数になる関数が読める程度に短ければ、関数の本体全部をmapの

呼び出しに埋め込むこともできる。これは関数を匿名関数にするということで、Javaプログ

ラマには馴染み深い響きであるはずだ。

1 -module(mapping).

2 -export([extract/1]).

3

4 extract(List) ->

5 lists:map(fun ( { _, Second, _} ) -> Second end, List).

図 17 匿名関数の使用例

foreach関数はmap関数と似ているが、値を返さないという点が異なる。

foldl関数と foldr関数は 1つの関数と 2つの追加引数をとる。foldの引数となる関数は

要素とアキュムレータの 2つの引数を取り、当該要素の値に基づいた新たなアキュムレータ

の値を返さなければならない。

foldlと foldrの関数引数ではないほうの 2つの引数はアキュムレータの初期値と fold

の対象となるリストだ。foldl関数はリストを最初から最後に向かってトラバースする。これ

Page 22: Thinking in Erlang (Japanese version)

に対して foldrは逆方向にリストをトラバースする。foldlは末尾再帰であるため、foldrより

も foldlを使うほうがよい。

以下は文字列中の aの数を数える例だ。

1 -module(count).

2 -export([count/1]).

3

4 count(String) ->

5 lists:foldl(fun (Element, Acc) ->

6 case Element of

7 $a ->

8 Acc + 1;

9 _->

10 Acc

11 end

12 end,

13 0,

14 String).

図 18 foldlの例

42> count:count("aabdca").

3

Figure 19: Using the example

図 19 foldlの例の使い方

4.3 手軽なループのまとめ

リストの各要素に対して(戻り値なしで)何らかのアクションを行う必要がある場合

は lists:foreachを使おう。

リストの各要素について何らかの計算を行う必要がある場合は lists:mapを使おう。

リストの各要素の値を累計する必要がある場合は list:foldlか list:foldrを使おう。

Page 23: Thinking in Erlang (Japanese version)

listsモジュールには他にもいくつかの手軽なループが提供されている。listsモジュー

ルのドキュメントに目を通して何が使えるかということを知っておくとよいだろう。

Page 24: Thinking in Erlang (Japanese version)

5 プロセス

5.1 二つのことを一度に行う

ちょうど Javaにスレッドがあるのと同様に Erlangにはプロセスが存在する。しかし

グローバルな状態というものが存在しないことから Erlangのスレッドはきわめて軽量なもの

となっている。マニュアルが言うように、Erlangは大規模な並列処理のために設計されてい

るのだ。

Java では run メソッドが定義された Thread のサブクラスを作り(あるいは

Runnableインターフェイスを実装したクラスを作り)、その startメソッドを呼ぶことによ

りスレッドを開始することができる。Erlangではそのような必要はない。エクスポートされ

た関数なら何でもエントリポイントとして使うことができ、関数が停止したところでプロセ

スも終了する。

プロセスを開始するには単純に別プロセスで実行したい関数の名前とその関数に渡す

引数を与えて spawnを呼べばよい。spawn関数は他の関数で利用できるプロセス IDを返

す。呼び出される関数の引数は渡された引数に対してマッチングされる。

Page 25: Thinking in Erlang (Japanese version)

1 -module(process1).

2 -export([main/0, thread/0]).

3

4 main() ->

5 Pid = spawn(process1, thread, []),

6 io:fwrite("Spawned new process ~w~n", [Pid]).

7

8 thread() ->

9 io:fwrite("This is a thread.~n", []).

10

図 20 プロセスを spawnする例

9> c("process1").

{ok,process1}

10> process1:main().

Spawned new process <0.65.0>

This is a thread.

ok

図 21 プロセスを spawnした結果

5.2 プロセス間通信

各プロセスはメールボックスを持っていて、これは受信したメッセージが入る単一の

キューだ。メッセージは何でもよい。文字列でもアトムでも整数でもタプルでもリストでも

何でも。プロセスに送るものとして最も便利なのはアトムとアトムが最初の要素に入ったタ

プルだ。このときアトムはメッセージ名で、他の要素は追加のメッセージデータになる。

プロセスは send構文を使って他のプロセスにメッセージを送信することができ、Pid

! Messageと書く。Pidが存在しなくても sendは成功する。

プロセスは receive構文を使うことによってメッセージを受信するまでブロックする

ことができる。これはちょうど caseのように使える。最初に受信したメッセージに対してマ

ッチさせる任意の数のパターンをとり、メッセージにマッチした最初のパターンの本体が実

Page 26: Thinking in Erlang (Japanese version)

行される。しかし caseとは違って、もしメッセージがマッチしなかった場合はそのメッセー

ジはキューに再配置され、プロセスはマッチするメッセージを待って再度ブロックする。

この点は重要だ。マッチしなかったメッセージは捨てられることはなく、他の receive

構文によってマッチされるときまでキューに残り続ける。

receive構文はオプションでミリセカンド単位のタイムアウト値を取ることもできる。

その時間経過後に receive構文の”after”節が実行される。この意味で after 節は単にマッチさ

れるパターンのひとつということになるが、receive構文の最後に来なければならないとい

う点だけが違う。詳細な文法についてはマニュアルを見てほしい。after 節の前にくる本体は

セミコロンでは終わらない。

Page 27: Thinking in Erlang (Japanese version)

1 -module(receive1).

2 -export([main/0, thread/0]).

3

4 main() ->

5 Pid = spawn(receive1, thread, []),

6 io:fwrite("Spawned new process _w_n", [Pid]),

7 Pid ! hello.

8

9 thread() ->

10 io:fwrite("This is a thread._n", []),

11 process_messages().

12

13 process_messages() ->

14 receive

15 hello ->

16 io:fwrite("Received hello_n"),

17 process_messages()

18 after 2000 ->

19 io:fwrite("Timeout._n")

20 end.

21

図 22 プロセス間通信の例

7> receive1:main().

Spawned new process <0.53.0>

This is a thread.

Received hello

hello

Timeout.

図 23 プロセス間通信の例の実行

process_messagesを末尾再帰にしているところに注目してほしい。

この出力で、新規プロセスを生み出し、プロセスが開始し、生み出されたプロセスが

helloメッセージを受け取り、元のプロセスが終了し(sendの戻り値は送信されたメッセー

Page 28: Thinking in Erlang (Japanese version)

ジなので出力は helloとなる)、最終的に 2 秒後に生み出されたほうのプロセスがタイムアウ

トしているのが分かる。

5.3 プロセスが停止する条件

ここまででプロセスを通常停止させるには単純にそのプロセスの関数を終了させれば

よいことが分かった。プロセスを即座に停止させるには exitや erlang:errorのような停止関

数を呼び出せばよい。これらは理由コードを引数に取る。理由コードを normalに設定すると

プロセスを通常停止することができる。

プロセスが normal 以外の理由によって停止した場合、リンクされたプロセス全てに

exit シグナルが送信される。一方のプロセスが他プロセスの IDで link関数を呼び出したと

き、その 2つのプロセスは(双方向に)リンクされていると言い、unlink関数を呼んでリン

クを解除することができる。このトピックについて詳しくは「エラー処理」の章を見てほし

い。

プロセスはプロセス IDと理由コードを指定して exit関数を呼ぶことによって他のプロ

セスを停止させることができる。しかしこの場合はそのプロセスが停止したときも exit シグ

ナルは送信されない。

Page 29: Thinking in Erlang (Japanese version)

1 -module(receive2).

2 -export([main/0, thread/0]).

3

4 main() ->

5 Pid = spawn(receive2, thread, []),

6 io:fwrite("Spawned new process ~w~n", [Pid]),

7 Pid ! hello,

8 exit(Pid, suckage).

9

10 thread() ->

11 io:fwrite("This is a thread.~n", []),

12 process_messages().

13

14 process_messages() ->

15 receive

16 hello ->

17 io:fwrite("Received hello~n"),

18 process_messages()

19 after 2000 ->

20 io:fwrite("Timeout.~n")

21 end.

22

Figure 24: Example of killing another process

図 24 他のプロセスを killする例

6> receive2:main().

Spawned new process <0.51.0>

This is a thread.

true

図 25 他のプロセスを killする例の実行

5.4 オブジェクトとしてのプロセス

Page 30: Thinking in Erlang (Japanese version)

プロセスをオブジェクトとみなすことも可能である。もしデータがエントリポイント

関数の引数に保存され、プロセスが受信するメッセージがそのデータに対するオペレーショ

ン指示であるとしたら、それは事実上オブジェクトだ。以下はオブジェクト内にデータをラ

ッピングするつまらない例で、コンストラクタ、デストラクタ、setメソッド、getメソッド

もついている。

Page 31: Thinking in Erlang (Japanese version)

1 -module(object).

2 -export([main/0, new/1, get/1, set/2, delete/1, construct/1]).

3

4 % Testing

5

6 main() ->

7 Object = new(1),

8 io:fwrite("Get data: ~w~n", [object:get(Object)]),

9 set(Object, 2),

10 io:fwrite("Get data: ~w~n", [object:get(Object)]),

11 delete(Object).

12

13 % Interface

14

15 new(Thing) ->

16 spawn(object, construct, [Thing]).

17

18 get(Object) ->

19 Object ! {get, self()},

20 receive

21 {return, Object, Thing} ->

22 Thing

23 end.

24

25 set(Object, Thing) ->

26 Object ! {set, self(), Thing}.

27

28 delete(Object) ->

29 exit(Object).

30

31 % Internals

32

33 construct(Thing) ->

34 io:fwrite("Called constructor: ~w~n", [Thing]),

35 process_flag(trap_exit, true),

Page 32: Thinking in Erlang (Japanese version)

36 process_messages(Thing).

37

38 process_messages(Thing) ->

39 receive

40 {get, Caller} ->

41 io:fwrite("Called get~n"),

42 Caller ! {return, self(), Thing},

43 process_messages(Thing);

44 {set, _Caller, NewThing} ->

45 io:fwrite("Called set~n", []),

46 process_messages(NewThing);

47 {’EXIT’, _Caller, _Reason} ->

48 io:fwrite("Called destructor~n", []),

49 true

50 end.

51

図 26 つまらないオブジェクト

2> object:main().

Called constructor: 1

Called get

Get data: 1

Called set

Called get

Get data: 2

** exited: <0.37.0> **

図 27 オブジェクトのお手並み拝見

get関数の同期的な部分に注意してほしい。他のプロセスからメッセージが来ていたと

しても処理されるまでそのメッセージ時はキューに残る。実際上は未知のメッセージをフラ

ッシュするために何にでもマッチするマッチを receive 節の最後に書いておいて、そこでメ

ッセージを破棄する(場合によってはその不正メッセージをログする)のがよいときもある

だろう。

Page 33: Thinking in Erlang (Japanese version)

8行目では正しく get関数を呼ぶためにモジュール名も明示している。これは大域的な

名前空間に get関数が存在するからだ。この関数は他の大域的な関数と同様に erlangモジュ

ールのドキュメントに記載されている。

33行目で引数が Thing ひとつであることに注意しよう。これは Thing ひとつのリスト

でプロセスを spawnしているためだ。もしオブジェクトが適切な型の引数で構築されている

かもチェックしたければガード条件を付け加えればよい。

このプログラムでは self関数も使用している。これは現在実行しているプロセスのプ

ロセス IDを取得するもので、Javaでいう Thread.currentThreadに相当する。

35行目はプロセスに対して exit シグナルを補足するように指示している。47行目で

オブジェクトを破棄するために標準の exitメッセージに対するマッチングを行っている。

5.5 汎用サーバ

サーバを実装したプロセスというのは適度に複雑なアプリケーションでは一般的であ

るため、Erlangはサーバ作成を容易にするための専用のモジュール gen_serverを提供して

いる。このモジュールはサーバが実装する標準的なインターフェイスや 1つないし複数のサ

ーバに同期的または非同期的にメッセージを送るための関数を用意している。さらに詳しい

情報はリファレンスマニュアルを見てほしい。

5.6 分散Erlang

プロセス IDは別に同一マシン上で走っているプロセスを参照するものでなくてもよ

い。Erlang シェルをリモートのマシンで名前(とオプションのホスト名または IPアドレス)

付きで起動すればそのノード名を使ってローカルマシンからそのノードに接続し、ノード名

を使ってプロセスを spawnし(これは実際にはリモートマシン上でプロセスを spawnす

る)、返されたプロセス IDに対してローカルマシン上のプロセスであるかのようにメッセー

ジを送ることができる。詳しくはリファレンスマニュアルを見てほしい。

この分散システムで組み込みのセキュリティは共有クッキーだけだということに注意

してほしい。ノードのクッキーを知らなければそのノードとは通信を行うことができない。

トランスポート層でのセキュリティは自動的には提供されないため、重要な情報を送受信し

Page 34: Thinking in Erlang (Japanese version)

ようとする場合には通信を SSLトンネルか他の暗号/認証メカニズム(たとえば Erlangの

sshライブラリ)でラッピングする必要があるだろう。

SSLトンネルを使用する場合、システムはローカルホスト以外からくる接続を遮断す

るように設定しなければならない。このアドバイスは企業で義務付けられるようなセキュリ

ティメカニズムについての筆者の経験のみに基づいている。セキュリティに携わる Erlangプ

ログラマは通信一般および Erlangのセキュリティについての最新情報をウェブでチェックす

ることが推奨される。

5.7 同期化は不要

Erlangには何と同期化のようなものが存在しない。またそれは必要でもないのだ。他

の言語で同期化を使う場合とは 2つのスレッドが同じグローバルなデータ要素を同時に改変

するのを防ぐときだということを考慮しよう。Erlangではグローバルなデータというものが

ないのだから明らかにこれが発生することはないのである。

Page 35: Thinking in Erlang (Japanese version)

6 エラー処理

6.1 「失敗するに任せよ」

もしエラーに対する Erlangの哲学をスローガンの形に要約することができるとした

ら、それは「失敗するに任せよ」といったものになるだろう。これが意味するところは、コ

ードを書くときに防御的なプログラミングはするなということだ。関数引数のマッチングの

話に戻ると、もし関数が整数の処理のみに使われるということを確証したいのであれば引数

が整数であることをチェックするガード条件を関数に付ければよい。ただし非整数のケース

をハンドリングするような追加の節は書かないことだ。

1 % これはよい

2

3 good(X) when is_integer(X) ->

4 X * 2.

5

6 % これはだめ

7

8 bad(X) when is_integer(X) ->

9 X * 2;

10 bad(X) ->

11 {error, "Argument to bad must be integer"}.

図 28 パターンマッチ中でのエラーハンドリングの良い例と悪い例

さて、これらの関数に非整数を渡したら何が起こるだろうか。よいほうの関数は

{badmatch,V}という終了理由で終わる。ここで Vは不正マッチを引き起こした値だ。他に

もスタックトレースが表示されてどのようにエラーが生じたかがわかるようになるだろう。

こ れ は Java の 関 数 に 不 正 な 引 数 を 渡 し た と き に 起 こ る こ と と 一 緒

Page 36: Thinking in Erlang (Japanese version)

だ。InvalidArgumentaExceptionが投げられるが、普通こうした例外をキャッチしたりはし

ない。

だめなほうの場合、関数は「正常に戻る」。そしてプロセスの停止は引き起こされ

ず、返されたタプルを使って何をするか決めるのは呼び出し元の関数に委ねられている。こ

れは誰にとっても意味のない余計なコーディングだ。

関数がユーザからの入力(たとえば GUIからの)を引数として取る場合は何が起こる

だろうか。その場合入力を検証するのは関数の側ではなく GUIハンドラに任されている。同

じ理屈が関数が他のプロセスから引数を得る場合にも当てはまる。実際いずれにせよ GUIハ

ンドラは別のプロセスということになるだろう。

6.2 作業プロセスと監督プロセス

プロセスは軽量なので機能をプロセスとして分割することを躊躇しないでほしい。も

しそのプロセスの寿命が短いとしても、だ。何か物事がうまくいかなかったときはプロセス

が異常停止し、そのエラーのハンドリングはリンクされたプロセスに委ねられる。もしリン

クされたプロセスがそのエラーをハンドリングできない場合は、リンクされたプロセスのほ

うも異常停止してさらに上位レベルのプロセスが問題をハンドリングできるようにすべき

だ。

これは Erlangで監督ツリー(supervision tree)として知られる考え方に通じる。これ

は実際に作業を行う「作業プロセス」と、作業を行わずに作業プロセスを監視する監督プロ

セス(supervisor)が存在するという考え方だ。

例を見てみよう。次にあげるプログラムでは接続を受け入れ、その後で各接続を作業

プロセスに手渡すようなサーバソケットをセットアップしようとしている。

Page 37: Thinking in Erlang (Japanese version)

1 -module(serverexample).

2 -export([start_server/0, handle_connection/1]).

3

4 start_server() ->

5 {ok, ListenSocket} = gen_tcp:listen(8080, [binary]),

6 accept(ListenSocket).

7

8 accept(ListenSocket) ->

9 {ok, Socket} = get_tcp:accept(ListenSocket),

10 spawn(server_example, handle_connection, [Socket]),

11 accept(ListenSocket).

12

13 handle_connection(Socket) ->

14 % do stuff

15

図 29 お手軽サーバ

サーバを開始するために gen_tcp:listenを使い、それを okに対してマッチングさせ

ている。もしエラーが起こった場合プロセスは異常停止する。その結果、このエラーのハン

ドリングは何らかの上位レベルのプロセスに委ねられる。それはサーバが生きているかどう

かを(たとえば死んでいた場合は再起動することによって)保証する監督プロセスというこ

とになるだろう。(図 30を参照)

こういうやり方で監督ツリーを構築することの一つの利点は、今述べた監督プロセスがより

上位のレベルの監督プロセスによって停止させられた場合、停止する前に子プロセスを停止

させるということだ。これにより自動的に安全なシャットダウンが行われる。

Page 38: Thinking in Erlang (Japanese version)

図 30 監督プロセスと子プロセス

サーバソケットを開くのに失敗したのでなく、新規接続を受け入れるのに失敗した

(get_tcp:acceptが okにマッチしなかった)としよう。この場合は監督プロセスがサーバ

を再起動するようにしたいが、既に接続を処理中かもしれない子プロセスはどうなるだろう

か。

接続を処理することだけを気にかけるのであれば問題はない。というのは、子プロセ

スは親プロセスとリンクされていない限りは親が停止しても停止することはなく、上記の例

ではプロセスをリンクしていないからだ(リンクする場合は spawn_linkを使う)。しか

し、サーバプロセスがいくつの接続が現在処理されているのかをトラッキングして、新規接

続が来たときにカウンタをインクリメントし、現存の接続が停止したときにカウンタをデク

リメントするようにしたいと考えてみよう。

機能を独立したプロセスにするのを躊躇すべきではないという哲学を思い出してほし

い。この場合は接続数をトラッキングする専用のプロセスを作るのだ。これは非効率的に聞

こえるかもしれないが、そんなことはない。Erlangのプロセスはとにかく軽量なのだ。カウ

ンタプロセスにメッセージを渡して接続カウンタを増減させるというやり方に全く悪いとこ

ろはない。(図 31を参照)

図 31 カウンタプロセスの追加

これでサーバが死んでもカウンタプロセスは生き続けて仕掛かりの接続の数を正しく

維持するようになる。

Page 39: Thinking in Erlang (Japanese version)

接続が停止するときにはカウンタプロセスにそう通知して接続数をデクリメントでき

るようにしてやる必要がある。我々はサーバプロセスを作業プロセスとして定義してあるの

でサーバプロセスに接続プロセスを監視させるのは賢明ではないだろう。そうするとサーバ

プロセスが作業プロセスでありかつ監督プロセスであるようなことになってしまうからだ。

正解はおそらく別個に接続監視のための監督プロセスを書くか、サーバを監督するプ

ロセスに接続プロセスの方も監視させるかのいずれかだろう。

残念なことにサーバの Erlang標準の supervisorモジュールにはリンクされたプロセ

スが停止したときにユーザ定義関数を呼ぶ機能がない。Erlangの supervisorの用途はいくつ

かの規定の方法のどれかを使ってプロセス停止をハンドリングすることだ。

しかし、我々は 2つのプロセスをリンクさせることにより停止シグナルが送られるこ

と、また上の方[訳注:プロセスをオブジェクト化する例]で見たように process_flagと

trap_exitを使ってこれらをハンドリングできることを知っている。カウンタプロセスがカウ

ンタをデクリメントするのにこのシグナルを使ってもいいわけだ。

ここまではサーバプロセスと接続プロセスが異常停止(そして正常停止)するような

条件を Erlang 流の考え方で扱ってきた。しかしまたカウンタプロセスを追加したことでその

カウンタプロセスについても監督プロセスが必要になってしまう。カウンタプロセスが異常

停止するとどうなるのだろうか。

この場合保持されていた接続数は当然失われてしまう。沢山ある戦略のうちどれを使

ってもよいが、最もシンプルな方法は既存の接続をすべて停止してゼロからやり直すことだ

ろう。これは自動的に起こる。というのはプロセス間のリンクは双方向だからだ。ハンドリ

ングできない exit シグナルを受信するとプロセスは終了する。

これが意味するところは、もしカウンタプロセスが停止し、かつそれが既存の接続プ

ロセスとリンクされている場合には、それら全てのプロセスが同様に停止するということ

だ。

もっと洗練された戦略としては接続プロセスは exit シグナルを無視するようにし、カ

ウンタプロセスが再起動したときに既存のプロセスをすべて探し出してリンクする、といっ

たものが挙げられるだろう。こうしたことができる pg2(プロセスグループ 2)と呼ばれる

モジュールが存在する。

Page 40: Thinking in Erlang (Japanese version)

プロセスは名前つきのプロセスグループに自身を追加することができ、他のプロセス

はそのグループに属するプロセスのリストを照会することができる。グループのメンバが停

止した場合、そのメンバは自動的にプロセスグループから除外される。(図 32を参照)

図 32 カウンタプロセスの監督と接続プロセスグループ

したがってカウンタプロセスは接続プロセスのグループに照会をかけてプロセスを取

得し、それぞれをリンクすることができる。面白いのはカウンタプロセスがある接続プロセ

スがグループの中に存在すると判断し、その後リンクしようとする前にその接続プロセスが

死んでしまった場合だ。この場合、カウンタプロセスは exit シグナルを受け取り、カウンタ

をデクリメントする。

Page 41: Thinking in Erlang (Japanese version)

参考文献

[1] http://www.erlang.org とそこで提供されたドキュメント

[2] http://www.erlang.se とそこで提供されたドキュメント。The Programming Rules

and Conventionsは綺麗な Erlangコードを書くためのすばらしいガイドだ。

Page 42: Thinking in Erlang (Japanese version)

用語索引

badmatch, 9

erlang:error, 26

exit, 26

exit シグナル, 26

foldl, 21

foldr, 21

foreach, 21

get, 29

map, 20

receive, 24

self, 29

send, 24

spawn, 23

アリティ, 6

ガードシーケンス, 15

ガード式, 16

ガード条件, 16

関数, 4, 5

関数型プログラミング言語, 4

監督ツリー, 33

監督プロセス, 33

シーケンシング, 17

束縛, 9

手続き型プログラミング言語, 4

プロセス, 23

メールボックス, 24

メッセージ, 24

モジュール, 5

リスト, 5

リンク, 26

Page 43: Thinking in Erlang (Japanese version)

図表索引

図 1 Hello, World........................................................................................................5

図 2 hello.erlの実行.....................................................................................................7

図 3 様々な変数............................................................................................................9

図 4 タプルのマッチング.............................................................................................11

図 5マッチングの失敗................................................................................................12

図 6 リストのマッチング.............................................................................................12

図 7 関数の引数におけるマッチング.............................................................................13

図 8 関数の候補とマッチング......................................................................................13

図 9 ifの例................................................................................................................14

図 10 caseの例.........................................................................................................14

図 11 ガードシーケンスの例........................................................................................15

図 12 カウンタの例....................................................................................................18

図 13 よくない再帰....................................................................................................19

図 14 適切な末尾再帰.................................................................................................19

図 15 mapの例.........................................................................................................21

図 16 mapの例の使い方.............................................................................................21

図 17 匿名関数の使用例..............................................................................................21

図 18 foldlの例..........................................................................................................22

図 19 foldlの例の使い方.............................................................................................22

図 20 プロセスを spawnする例...................................................................................24

図 21 プロセスを spawnした結果...............................................................................24

図 22 プロセス間通信の例...........................................................................................25

図 23 プロセス間通信の例の実行.................................................................................25

図 24 他のプロセスを killする例..................................................................................27

図 25 他のプロセスを killする例の実行........................................................................27

図 26 つまらないオブジェクト....................................................................................29

図 27 オブジェクトのお手並み拝見..............................................................................29

図 28 パターンマッチ中でのエラーハンドリングの良い例と悪い例.................................32

図 29 お手軽サーバ....................................................................................................34

図 30 監督プロセスと子プロセス.................................................................................35

Page 44: Thinking in Erlang (Japanese version)

図 31 カウンタプロセスの追加....................................................................................35

図 32 カウンタプロセスによる監督と接続プロセスグループ...........................................37