Rubyist のための他言語探訪 【第 10 回】 Erlang

著者:まつもとゆきひろ

Erlang について

今回は関数型言語 Erlang について紹介しようと思います。

RubyConf 2006 の Welcome セッションで、スタッフの一人である Rich Kilmer の「最近のお気に入り」であるとして、Erlang の紹介ビデオが流されました。 電話交換機プログラムに使われている Erlang を解説する 80 年代風のビデオは (実際に 80 年代に撮られたビデオのようですが)、まるでモンティパイソンのようでした。

Erlang は 1987 年頃、スウェーデンの電話会社である Ericsson で開発された関数型言語です。 Erlang の名前は、デンマーク出身の数学者・統計学者・技術者である Agner Krarup Erlang にちなんで名付けられています。 発音は「あーらん」という感じのようです。 私は最初「Ericsson の言語」という意味だろうと思い「えるらんぐ」と発音してましたが、発音については明確に間違いですね。 ただ、開発者たちも「Ericsson の言語」というニュアンスは意識したようです。 なお、Erlang は通信におけるデータのトラフィック量 (通信量) を表す国際単位 (略号 erl) にもなっています。

Erlang の特徴は以下の通りです。

  • 関数型
  • 単一代入
  • パターンマッチ
  • 動的型
  • プロセス
  • 耐障害性

ではひとつひとつ見てみましょう。

単一代入

Erlang の変数は一度だけ代入可能です。 同じ変数に複数回の代入を行おうとするとエラーになります。 これはつまり、通常のループなどは書けないということを意味します。 実際、Erlang ではループの類は再帰か高階関数を用いて実現します。 この辺は関数型言語らしいと言えるかもしれません。

Erlang の変数はすべて大文字またはアンダースコア (_) で始まることになっています。 変数名にルールがあるところはちょっとだけ Ruby を思い出させます。

動的型

同じ関数型でも型推論のある OCaml や Haskell とは異なり、Erlang には静的型がありません。 型の解決は動的に行われます。 イギリスで開発された ML の型推論が、同じくイギリスで始まった Haskell やフランスで開発された OCaml などに受け継がれてきたのとは違い、北欧生まれの Erlang は関数型言語の別の血脈であると考えればよいのでしょうか。 しかし、北欧というのは人口はさほど多くないはずですが、Simula といい、C++ といい、Beta といい、この Erlang といい、ユニークなプログラミング言語が生まれる土地ですねえ。

関数型

Erlang は関数型言語です。つまり、

  • 状態がない (副作用がない)
  • 第一級オブジェクトとしての関数
  • 高階関数プログラミング

などの関数型言語としての特徴を備えています。

Erlang のプログラム

では、実際に Erlang のプログラムを見てみましょう。 以下は Erlang による階乗を求めるプログラムです。

 -module(fact).
 -export([fac/1]).

 fac(0) -> 1;
 fac(N) when N > 0 -> N * fac(N-1).

Erlang にはきちんとしたモジュールシステムがあり、最初の 2 行はモジュールの宣言になっています。 1 行目は fact というモジュールを宣言し、2 行目で 1 引数の fac という関数をエクスポートしています。

Quicksort のプログラムです。

 -module(quicksort).
 -export([qsort/1]).

 qsort([]) -> [];
 qsort([Pivot|Rest]) ->
     qsort([ X || X <- Rest, X < Pivot]) ++ [Pivot] ++ qsort([ Y || Y <- Rest, Y >= Pivot]).

最初の 2 行は先ほどと同様にモジュールの宣言です。 残りの qsort の実体は関数型言語を見たことがあればごく自然に読めるのではないでしょうか。 というか、正直、Haskell の quicksort プログラムとほとんど同じです。 引数でパターンマッチするあたりも同じですね。リスト閉包記法までよく似ています。

さきにも述べたように Erlang にはループ構文はありませんが、if 文と case 文はあります。

 odd(X) ->
 	if
 	  X rem 2 == 0 -> true;
 	  true -> false
 	end.

if 文は「式 -> 式」を並べたものです。 else に当たるものが存在しないので、true を置きます。 なんか変ですが、確か Beta も似たような文法だったように思います。 北欧的伝統かしら。

case 文も似たような文法です

  case X of
    1 -> "hogehoge";
    2 -> "fugafuga";
    3 -> "foobar";
    _ -> "zzz..."
  end

case では他の関数型言語同様パターンマッチを使うことができます。

プロセス

「プロセス」といっても OS が管理するプロセスとは違います。 他の言語で言うと「スレッド」のような単一の OS レベルプロセスの中で実行される複数の実行の流れのことです。 ただし、Erlang にはグローバル変数も書換可能なオブジェクトもありませんから、これらの制御の流れの間で共有される状態というものが存在しません。 この共有される状態というのはスレッドとプロセスを分ける重要な要素であり、Erlang のそれは共有状態を持たない以上、「プロセス」であるというのが開発者の主張のようです。 Erlang のプロセスは大変軽量であることで知られており、1 プロセスあたり 300 バイト程度しかメモリを消費せず、ある実験では 2000 万個のプロセスを同時に実行することが可能であったとのことです。

プロセス間で共有する状態がないということは、プロセスは単一 OS プロセスに限定される必要がないということも意味します。 Erlang のプロセスは複数の OS プロセスにまたがって存在することも可能であり、分散処理にも対応しています。

Erlang のプロセス間通信は「メッセージ」によって行われます。 Erlang の任意の値をメッセージとして用いることができます。 メッセージの送出は「!」演算子によって行い、メッセージの受け取りは receive 文を用いて行います。

なお、Erlang では関数型言語の枠組みでは処理が難しい入出力や他言語インタフェースもこのメッセージによって実現しています。

以下はメッセージセンドの基礎を表現する例です。 start 関数を実行すると、ping プロセスと pong プロセスの二つを spawn し、ふたつのプロセスの間で 15 回メッセージをやりとりします。

 -module(pingpong).
 -export([start/0, ping/2, pong/0]).

 ping(0, Pong_PID) ->
     Pong_PID ! finished,
     io:format("ping finished~n", []);

 ping(N, Pong_PID) ->
     Pong_PID ! {ping, self()},
     receive
 	  pong ->
 	      io:format("Ping received pong~n", [])
     end,
     ping(N - 1, Pong_PID).

 pong() ->
     receive
 	  finished ->
 	      io:format("Pong finished~n", []);
 	  {ping, Ping_PID} ->
 	      io:format("Pong received ping~n", []),
 	      Ping_PID ! pong,
 	      pong()
     end.

 start() ->
     Pong_PID = spawn(pingpong, pong, []),
     spawn(pingpong, ping, [3, Pong_PID]).

pingpong はあまりにも人工的な例なので、もうちょっとだけ実用をイメージできる例も紹介しましょう。

 -module(fibcalc).
 -export([calc_fibs/1, fib_send/2, start/0]).

 % [a,b,c,...] を受け取って fib(a), fib(b), ... を表示
 calc_fibs( Lst ) ->
 	  calc_fibs_list( Lst ),
 	  receive_results( length(Lst) ).


 % フィボナッチ数列 {1,1,2,3,5,8,13,...} の計算
 fib(0) -> 1;
 fib(1) -> 1;
 fib(N) -> fib(N-1) + fib(N-2).
 fib_send(P,N) -> P ! {N,fib(N)}. % 計算結果をプロセス P へ送る版

 % リストの各要素から fib を計算するプロセス生成
 calc_fibs_list([])      -> ok; % 何もしない
 calc_fibs_list([N|Lst]) ->
 	  spawn(fibcalc, fib_send, [self(),N]),
 	  calc_fibs_list(Lst).


 % N 個の計算結果を受け取る
 receive_results(0) -> complete; % 何もしない
 receive_results(N) ->
 	  receive
 		  {X, Fx} ->
 			  io:format("fib(~w) = ~w~n", [X, Fx]),
 			  receive_results( N-1 )
 	  after 1000 ->
 		  timeout
 	  end.

 start() -> calc_fibs([12, 3, 11, 4, 24, 31, 28, 10]).

これを実行した結果は以下のようになります。

 fib(12) = 233
 fib(3) = 3
 fib(11) = 144
 fib(4) = 5
 fib(10) = 89
 fib(24) = 75025
 fib(28) = 514229
 fib(31) = 2178309
 complete

フィボナッチ数の計算はプロセスによって並列して実行されるため、計算量によって結果の出力順が入れ代わっていることに注目してください。 なお、この例は http://www.kmonos.net/alang/etc/erlang.php からお借りしました。

耐障害性

Erlang は元々電話交換機のような信頼性が要求される環境のために設計されたプログラミング言語なので、エラーによってプログラム全体の動作が停止しないように設計されています。 あるプロセスで発生した例外はそのプロセスだけを停止させ、デバッガーを起動させます。 そこでプログラムを修正し、そのモジュールを再ロードさせてから続きを実行させることもできます。

まとめ

今回はまだあまり知られていない関数型言語 Erlang について解説しました。 Erlang は知名度こそ低いものの Ericsson 内部を中心に相当の実績を積んでいます。 また、ユーザカンファレンスも毎年開かれているようです。 2006 年は 11 月 9 日、10 日にストックホルムで第 12 回国際 Erlang ユーザカンファレンスが開催されたそうです。 1998 年以来 Erlang 処理系はオープンソースとして公開されていますし、Debian のパッケージも存在します。 関数型言語としては最近 Haskell が注目されていますが、軽量プロセスと高信頼性を特徴とする Erlang も面白いかもしれません。

Erlang についての情報は以下から入手できます。

Wikipedia 英語版
http://en.wikipedia.org/wiki/Erlang_%28programming_language%29
Wikipedia 日本語版 (英語版の翻訳)
http://ja.wikipedia.org/wiki/Erlang
Open Source Erlang
http://www.erlang.org/
Erlang Land (日本語解説)
http://www.kmonos.net/alang/etc/erlang.php

著者について

matz_in_suit.jpgまつもとゆきひろは自他ともに認める日本を代表する言語オタクです。 言語好きが昂じて自分の言語を設計してしまった大馬鹿者です。 が、オタクとかハッカーとか呼ばれる人種はみんな多かれ少なかれそんなものじゃないでしょうか。