プログラミング言語Erlangとその応用事例

プログラミング言語Erlangと
その応用事例
檜山正幸 (HIYAMA Masayuki)
2008年10月23日(木)
おおざっぱな予定
1. 前半:Erlangの紹介 18:30-
2. 前半の質疑応答 19:00-
3. 後半:Web Comunication Channelsの紹介
19:15-
4. 後半の質疑応答 19:45-
2
プログラミング言語Erlang
Erlangは関数型言語?
Erlangはプロセス指向言語!
Erlangはメッセージ指向言語!
Erlangは並列指向言語!
Erlangは分散指向言語
Erlangのランタイムシステムはすごい!?
文末記号の意味:
?
今日は強調しない。あまり触れない。
!
強調したい。いろいろな言い方されるが根は一緒。
!? 確かにそうだが、よくわからない(からあまり触れない)。
無印 特に話題にしないが、推測できるでしょ(たぶん)。 3
(サンプルの部)
Erlangプログラムの編成:なんだかんだ
 ! モジュールは関数の集まり mod:func(arg1, arg2)
 モジュール名、ソースファイル名、実行可能(BEAM)ファイル名
は同一
 論理的な概念(モジュール)と物理的な概念(ファイル)を混同し
ても弊害は少ない
 ! ランタイムシステムには、モジュールが(原則)動的にロード
される
 モジュールの集まりを”アプリケーション”と呼ぶ(要注意!)
5
Erlangプログラムの編成:なんだかんだ (2)
 アプリケーションの物理的実体は、モジュールを格納している
ディレクトリ
 アプリケーションメタデータもあるが、小規模なアプリケーション
ではメタデータ不要
 システムはアプリケーション(モジュール集合)と、どのアプリ
ケーションにも属さない野良モジュールからなる。
 パッケージ機構(モジュールに階層的名前空間を提供)もある
が、使っている例を見たことがない。
 ! 関数名は、同一モジュール内なら裸の名前、他のモジュー
ルならコロンで修飾
 import機構があるが、「使わないほうがよい」とされている
6
Erlangプログラムの編成:だから結局
(図:ここでホワイトボードに図を描きます。)
7
Erlangのプロセス
どんな関数でも、とあるプロセスで実行される
換言すると、プロセスがないと関数が実行できない
一方、プロセスは1つの関数(メイン)を実行するため
にある
もちろん、1つの関数から呼ばれた関数も同じプロセス
で実行される
プロセスのメイン関数が終わればプロセスも終わる
(消滅)
Erlangでは、関数だけでなく、プロセスが明白に意識され
市民権を持ち、 いくらでも(誇張あり)プロセスを使える。
8
ここでなぜかHelloWorld
%% d.erl -*- coding:utf-8 -*-module(hello).
-compile(export_all).
do() -> spawn(fun()->io:format("Hello, world.~n") end).
do(N) -> spawn(?MODULE, hello, [N]).
forever() -> spawn(fun helloever/0).
hello(0) -> ok;
hello(N) when N > 0 ->
io:format("Hello, world.~n"),
timer:sleep(500),
hello(N - 1).
helloever() ->
io:format("Hello, world.~n"),
timer:sleep(500),
helloever().
9
ここでなぜかHelloWorld (2)
ErlangShell(という名の対話的プロセス)から hello:do().
とすると:
1. Shellプロセスの管理下で hello:do() が実行される。
2. do()からプロセスが生成される。
3. 生成されたプロセスにより、1回Helloを表示する。
4. それだけでプロセスは正常終了。
hello:do(10). とすると:
1. 基本的に同じことが起きるが、プロセスはやや長
時間の寿命を持つ。
10
ここでなぜかHelloWorld (3)
再帰呼び出しに制限を設けず、長時間を無限に伸ば
すとリアクティブなプロセスになる。
寿命の観点から、プロセスを次の2種に分類するのが
現実的:
ある一定時間(予測はできなくても)後に仕事を終え
るプロセス。
原則として無限に動き続け、なんかのはずみで終
わるかもしれないプロセス。
11
Erlangのプロセスって何?
確かにOSプロセスの概念に近い
「とっても小さなコンピュータ」という比喩が有効
CPU(リダクション実行主体)とスタックとヒープ
1個のプロセスは、1個のCPUと1つのスタックで逐次的
に動く
生まれて、動いて/働いて、消える
数マイクロ秒で生成可能
普通に数万から数十万/ランタイム
参考: http://lab.klab.org/wiki/Erlang_Process
「4000万個起動することが出来ました」
12
Erlangのプロセスって何? (2)
 実行コードは、関数/モジュールというまとまりに編成されている
 スタック上で関数フレーム達が伸びたり縮んだり
 ヒープにはデータの実体が確保されたり解放されたり
 この小さなコンピュータ達はネットワーク環境にいる
 固有ID(アドレス)を持っている
 名前(公開ホスト名)を持ってもいい/持たなくてもいい
 互いに直接的にいきなりパケット(?)通信できる
 パケットに例えたモノはErlangターム(任意)
 バッファリングするNIC(?)を持っている、メッセージキュー/メー
ルボックス
13
Erlangのプロセスって何? (3)
メモリ空間は互いに完全に分離されている
原則的に(例外はあるが)共有メモリはない
実行コード(関数)は共有、公共的で特に所有者プロセ
スはない
NICに相当するメッセージキューへのポストは直列化さ
れる
メッセージ以外の相互作用はなく、プロセス達は独立
に動作する
14
Erlangのプロセスって何? (4)
各プロセス(小さなコンピュータ)は、ほぼ同一の性能
実際にはランタイム内で、できるだけ公平にスケ
ジュールされる
リソース割り当て(空間の分配)とスケジューリング
(時間の分配)の対象/単位が同じ
1個のプロセスを見てる限りは、並列性を意識する必
要はない。
メッセージのデータは、異なるメモリ空間にコピーされ
ると解釈される。実際はそうでもないけど(理由:イ
ミュータブル)。
15
メッセージ・データの移動
図-移動(ホワイトボード)
16
メッセージング・グラフ
理論的には完全有効グラフだが、相手を知ってないと
メッセージを送れないので、 「知っている」関係で通信
の経路は制限される。
メッセージデータは、特定時点のメッセージンググラフ
の辺に沿って伝送される。 全プロセスをノードするメッ
セージング・グラフは時々刻々と変化する。
図-メッセージング (ホワイトボード)
17
リンクとシグナルのグラフ
リンクされたプロセスは、片方が死ぬともう一方も死ぬ
exitシグナル、errorシグナルがリンクに沿って伝搬
シグナルはメッセージと別物
だが、メッセージとして捕捉可能
リンクの設定は、「linkするほう→されるほう」で
方向を持つ。が、シグナルはその方向に無関係。
18
リンクとシグナルのサンプル
%% d06.erl -*- coding:utf-8 -*-module(d06).
-compile(export_all).
start() -> spawn(fun parent_main/0).
parent_main() ->
process_flag(trap_exit, true),
Pid = spawn_link(fun child_loop/0),
parent_loop(Pid).
19
リンクとシグナルのサンプル (2)
parent_loop(Pid) ->
receive
s -> % stop
exit(Pid, kill), % 通常、killは乱用してはいけない
io:fwrite("Parent: byebye.~n"),
exit(normal); % 明示的にexitを呼んでもよい
{'EXIT', Pid, Why} -> % シグナルから変換されたメッセージ
io:fwrite("Parent: Child ~p exited. :~p~n", [Pid, Why]),
NewPid = spawn_link(fun child_loop/0),
parent_loop(NewPid);
Message ->
Pid ! Message, % そのまま子供に転送
parent_loop(Pid)
end.
20
リンクとシグナルのサンプル (3)
child_loop() ->
receive
{error, Term} ->
io:fwrite("Child ~p:
erlang:error(Term),
child_loop();
{exit, Term} ->
io:fwrite("Child ~p:
exit(Term),
child_loop();
{throw, Term} ->
io:fwrite("Child ~p:
throw(Term),
child_loop();
Other ->
io:fwrite("Child ~p:
child_loop()
end.
'error' received.~n", [self()]),
'exit' received.~n", [self()]),
'throw' received.~n", [self()]),
Unknown message:~p~n", [self(), Other]),
21
よく出来た資料があるので拝借
特に、アニメートするプロセスの図がわかりやすい。
Erlang.ppt
22
Erlangでアプリケーションを作るには
関数達の静的な構造はモジュール(+アプリケーショ
ン)として編成
実行時のプロセス達の生成消滅のシナリオを考える
メッセージグラフ、リンクグラフとしてプロセス達の相互
関係を考える
メッセージのプロトコル、シグナルのプロトコルを考え
る
23
Erlangでアプリケーションを作るには (2)
基本的なフレームワークはOTPライブラリが準備して
いる。
メッセージプロトコルは、RPCのAPIやイベント配信とし
て考えるとよい
高水準のAPIを(例えばIDLで)決めれば、あとはOTP
が面倒みてくれる
参照:
http://d.hatena.ne.jp/m-hiyama/20070712/1184213007
リンクグラフの構成とシグナルのプロトコルもOTPが準
備している(ワーカー/スーパーバイザ・モデル)
24
Erlangでアプリケーションを作るには (3)
図(ホワイトボード)
基本素材:関数、プロセス、メッセージ
高級でマクロな素材:RPC、イベント、クラアント/
サーバ、スーパーバイザ・ツリー
ユーザーレベルでのAPI、プロトコル
アプリケーション
25
Web Communication Channels
の紹介
http://www.microapplications.net
26
ことの発端
Webアプリケーションとはいうが
作る(作らせる)のはWebサイト所有者
ブラウザ利用者はサイトと独立にアプリケーションを
作れないの?
27
ブラウザ上のアプリケーション
GUIはHTMLレンダリングエンジン
実装言語はJavaScript
ストレージがない
実は自発的に通信もできない
Ajaxは?
サイトに依存し縛られる
28
通信機能とストレージ
中立な中継サイトを設けて、できるだけ透過的にブラ
ウザ-ブラウザ通信をサポートする。
永続的なストレージも提供する。
クロスドメインHTTP通信が必須
現状、変な方法しかない
悲しいがしょうがない
逆方向(サーバー ⇒ ブラウザ)通信も必要
COMET
参考:
http://d.hatena.ne.jp/m-hiyama/20080528/1211950144
29
別な動機
サーバー側もJavaScriptで書けたらいいじゃない?
既にあるけど流行ってない
JavaScriptプログラムがローミングしたら面白いのでは?
そりゃ無理でしょ
JavaScriptの小さな小さなサブセットくらいなら、、、、
この未練は今でも尾を引いている
30
Erlangがお手本
単に実装言語としてだけでなく、分散モデルも借用。
Erlang
ノード
プロセス
PID
メッセージ
ターム
gen_server Call
gen_server Cast
WCC
ブラウザもノード
エージェント(動作主
体)
エージェントID(AID)
マイクロ・メッセージ
JSONデータ
RGenic Call
RGenic Cast
31
アリモノをツギハギ
JavaScriptのイベントモデルはW3C DOM3から
外部からのコールバックはActionScriptの
ExternalInterface
データ形式は徹底的にJSON、ただし抽象的データ形
式として
OMG IDLのサブセットで仕様記述の予定(全然できて
ない)
100%コンフォーマントはめざしてないが、それでも標準と
折り合いを付けるのは大変。
用語の混乱やネーミングの理不尽さに泣く。
32
COMET
原理は簡単
やってみると、ブラウザごとの挙動の違いに泣く
JavaScriptのシングルスレッドにも泣く -- IOブロックと
かsleepしてポールとかができない
On Demand JavaScript方式(a.k.a JSONP)とCOMET
を組み合わせて、なんとか双方向通信
33
COMETは資源を消費する
が、Erlangなら、、、
http://www.sics.se/~joe/apachevsyaws.html
Apache vs. Yaws
Apache が同時接続数約4千で応答なし、Yaws
同時接続数8万以上まで応答。
http://groups.google.com/group/comp.lang.function
al/msg/33b7a62afb727a4f?dmode=source
2000万個のプロセス(64-bit erlang on a 1.5 GHz
SPARC with 16 GB RAM)
34
Erlangで作ったことは
たしかに考えやすい、作りやすい
他の言語とものすごく変わるわけではない
スレッドのような難しさはない
静的な型概念、型チェックがないのは痛いときもある
YAWSというプラットフォームもあることだし、Webアプリ
ケーションには向いているのではないか
逆に、Web的手法がErlangプログラミングに生かせるこ
ともある
35
Erlangで作ったことは (2)
500メッセージ/秒くらいはさばけそう
フォールトトレランスや実行時コード置き換えは今後
最近やっと「不要なことは書かない」「いさぎよく死ぬ」
「一人で死ぬ」が分かってきた
これは、Erlangのポリシーで納得が難しい部分
36
アプリケーション
ジャンケン大会が最初設定した目標だった
多人数でやるクイズやゲーム
同時アンケート/投票
ブラウザ・サーバー(今日、「神社を作れ」と)
ブラウザ不在時はスクリプトで動く
37
問題
クロスドメイン通信のような基盤があやしい
セキュリティ :よくわかんない
モラル : これも問題になるかもしれない
クラスター構成 : まだやってないので未知
監視 : ライブラリがあれども事例がない
なにがうれしい : さあ?
← 大問題では
38
(おわり)
サンプルの部
まずはサンプルとデモ
• 細かいことは気にしない。
• なんとなく雰囲気がわかればよい。
• 後でまた説明します。
41
関数を定義してみる
%% d01.erl -*- coding:utf-8 -*%% 再帰関数
-module(d01). % モジュールの宣言
-export([fact/1, append/2]). % エクスポートする関数の宣言
%% 階乗
fact(0) ->
1; % Prologerはセミコロンである点に注意
fact(N) when N > 0 -> % when ... はガード
N * fact(N -1).
%% リストの連接
append([], List) ->
List;
append([First|Rest], List) ->
[First|append(Rest, List)].
42
データとしての関数(fun)
%% d02.erl -*- coding:utf-8 -*%% 高階関数、ラムダ式
-module(d02).
-compile(export_all). % コンパイラに全部エクスポートするように指令
sumup(From, To, Fun) -> % 第3引数にfun
lists:sum(
lists:map(Fun, lists:seq(From, To))).
make_inc(N) -> % 戻り値がfun
fun (X) -> X + N end. % '->' を忘れるのだ(檜山だけ?)
sq(N) -> % テスト用、平方する関数
N * N.
43
いたるところにパターンマッチ
%% d03.erl -*- coding:utf-8 -*%% パターンマッチ
-module(d03).
-compile(export_all).
p1({Name, Age}) -> % 既にお馴染み、引数にパターン
io:format("Your name: ~s~n", [Name]), % カンマは順次実行
io:format("Your age: ~p~n", [Age]).
p2({Name, Age}) when is_list(Name), is_number(Age) -> % ガードで制約をきつく
io:format("Your name: ~s~n", [Name]),
io:format("Your age: ~p~n", [Age]);
p2(Name) when is_list(Name) -> % 別なパターン&ガード
io:format("Your name: ~p~n", [Name]).
p3(X) -> % p3と同じ、case式はもっともよく使われる制御構造
case X of
Name when is_list(Name) ->
io:format("Your name: ~s~n", [Name]);
{Name, Age} when is_list(Name), is_number(Age) ->
io:format("Your name: ~s~n", [Name]),
io:format("Your age: ~p~n", [Age])
end.
44
%% d05.erl -*- coding:utf-8 -*%% 自発的に動き続けるプロセス
-module(d05).
-compile(export_all).
プロセス作って
メッセージ送ってみる
start() ->
spawn(fun main/0).
main() ->
receive
b -> % break
break(); % 再帰じゃないけど末尾呼び出し (last call)
s -> % stop
io:format("stop.~n"); % プロセスも自然終了
_Other ->
main() % キューのフラッシュ
after 500 -> % 0.5秒ごとに
io:format("Hello.~n"),
main() % 末尾再帰
end.
break() ->
receive
c -> % continue
main(); % これでmainに戻る
_Other ->
io:format("break and stop.~n") % 終わり
end.
45
リアクティブな
プロセス
%% d05.erl -*- coding:utf-8 -*%% 自発的に動き続けるプロセス
-module(d05).
-compile(export_all).
start() -> spawn(fun main/0).
main() ->
receive
b -> % break
break(); % 再帰じゃないけど末尾呼び出し (last call)
s -> % stop
io:format("stop.~n"); % プロセスも自然終了
_Other ->
main() % キューのフラッシュ
after 500 -> % 0.5秒ごとに
io:format("Hello.~n"),
main() % 末尾再帰
end.
break() ->
receive
c -> % continue
main(); % これでmainに戻る
_Other ->
io:format("break and stop.~n") % 終わり
end.
46
なぜかJavaクラスを出してみる
/* Counter.java */
public class Counter {
private int count; // 内部状態
public Counter(int init) {
count = init;
}
public void up() {
count++; // 状態の変更
}
public void down() {
count--; // 状態の変更
}
public int value() {
return count; // 問い合わせに応える
}
}
47
Erlangならこうなる
%% counter.erl -*- coding:utf-8 -*%% カウンタ
-module(counter).
-compile(export_all). % お行儀悪い
%% new Constructor(init) 相当
start(Init) ->
spawn(?MODULE, main, [Init]).
start(Name, Init) ->
register(Name, spawn(?MODULE, main, [Init])).
main(Count) ->
receive
up -> % up() 相当
main(Count + 1);
down -> % down() 相当
main(Count - 1);
{value, Pid} -> % value() 相当
Pid ! {count, Count}, % 値をメッセージで返す
main(Count);
s -> % 終了
ok;
_Other -> % 無視
main(Count)
end.
48