Upload
loren
View
113
Download
0
Embed Size (px)
DESCRIPTION
プログラミング言語 Erlang と その応用事例. 檜山正幸 (HIYAMA Masayuki) 2008 年 10 月 23 日 ( 木 ). おおざっぱな予定. 前半: Erlang の紹介 18:30 - 前半の質疑応答 19:00 - 後半: Web Comunication Channels の紹介 19:15 - 後半の質疑応答 19:45 -. プログラミング言語 Erlang. Erlang は関数型言語 ? Erlang はプロセス指向言語 ! Erlang はメッセージ指向言語 ! Erlang は並列指向言語 ! - PowerPoint PPT Presentation
Citation preview
プログラミング言語Erlang とその応用事例
檜山正幸 (HIYAMA Masayuki)2008 年 10 月 23 日 ( 木 )
2
おおざっぱな予定
1. 前半: Erlang の紹介 18:30 - 2. 前半の質疑応答 19:00 -
3. 後半: Web Comunication Channels の紹介19:15 -
4. 後半の質疑応答 19:45 -
3
プログラミング言語 ErlangErlang は関数型言語? Erlang はプロセス指向言語! Erlang はメッセージ指向言語! Erlang は並列指向言語! Erlang は分散指向言語 Erlang のランタイムシステムはすごい!?
文末記号の意味:? 今日は強調しない。あまり触れない。 ! 強調したい。いろいろな言い方されるが根は一緒。 !? 確かにそうだが、よくわからない(からあまり触れない)。 無印 特に話題にしないが、推測できるでしょ(たぶん)。
(サンプルの部)
5
Erlang プログラムの編成:なんだかんだ
! モジュールは関数の集まり mod:func(arg1, arg2) モジュール名、ソースファイル名、実行可能( BEAM )ファイル名は同一
論理的な概念(モジュール)と物理的な概念(ファイル)を混同しても弊害は少ない
! ランタイムシステムには、モジュールが(原則)動的にロードされる
モジュールの集まりを”アプリケーション”と呼ぶ(要注意!)
6
Erlang プログラムの編成:なんだかんだ (2)
アプリケーションの物理的実体は、モジュールを格納しているディレクトリ
アプリケーションメタデータもあるが、小規模なアプリケーションではメタデータ不要
システムはアプリケーション(モジュール集合)と、どのアプリケーションにも属さない野良モジュールからなる。
パッケージ機構(モジュールに階層的名前空間を提供)もあるが、使っている例を見たことがない。
! 関数名は、同一モジュール内なら裸の名前、他のモジュールならコロンで修飾
import 機構があるが、「使わないほうがよい」とされている
7
Erlang プログラムの編成:だから結局
(図:ここでホワイトボードに図を描きます。)
8
Erlang のプロセス
どんな関数でも、とあるプロセスで実行される 換言すると、プロセスがないと関数が実行できない
一方、プロセスは 1 つの関数(メイン)を実行するためにある
もちろん、 1 つの関数から呼ばれた関数も同じプロセスで実行される
プロセスのメイン関数が終わればプロセスも終わる(消滅)Erlang では、関数だけでなく、プロセスが明白に意識され市民権を持ち、 いくらでも(誇張あり)プロセスを使える。
9
ここでなぜか 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().
ここでなぜか 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 シグナルがリンクに沿って伝搬
シグナルはメッセージと別物だが、メッセージとして捕捉可能
18
リンクの設定は、「 link するほう→されるほう」で方向を持つ。が、シグナルはその方向に無関係。
リンクとシグナルのサンプル
19
%% 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).
リンクとシグナルのサンプル (2)
20
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.
リンクとシグナルのサンプル (3)
21
child_loop() -> receive {error, Term} -> io:fwrite("Child ~p: 'error' received.~n", [self()]), erlang:error(Term), child_loop(); {exit, Term} -> io:fwrite("Child ~p: 'exit' received.~n", [self()]), exit(Term), child_loop(); {throw, Term} -> io:fwrite("Child ~p: 'throw' received.~n", [self()]), throw(Term), child_loop(); Other -> io:fwrite("Child ~p: Unknown message:~p~n", [self(), Other]), child_loop() end.
よく出来た資料があるので拝借
特に、アニメートするプロセスの図がわかりやすい。 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.net26
ことの発端
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 がお手本
単に実装言語としてだけでなく、分散モデルも借用。
31
Erlang WCCノード ブラウザもノードプロセス エージェント(動作
主体)PID エージェント
ID ( AID )メッセージ マイクロ・メッセー
ジターム JSON データgen_server Call RGenic Callgen_server Cast RGenic Cast
アリモノをツギハギ
JavaScript のイベントモデルは W3C DOM3 から
外部からのコールバックは ActionScript のExternalInterface
データ形式は徹底的に JSON 、ただし抽象的データ形式として
OMG IDL のサブセットで仕様記述の予定(全然できてない)
32
100% コンフォーマントはめざしてないが、それでも標準と折り合いを付けるのは大変。用語の混乱やネーミングの理不尽さに泣く。
COMET原理は簡単やってみると、ブラウザごとの挙動の違いに泣くJavaScript のシングルスレッドにも泣く -- IO ブロックとか sleep してポールとかができない
On Demand JavaScript 方式( a.k.a JSONP )と COMET を組み合わせて、なんとか双方向通信
33
COMET は資源を消費する
が、 Erlang なら、、、 http://www.sics.se/~joe/apachevsyaws.html
Apache vs. YawsApache が同時接続数約4千で応答なし、 Yaws 同時接続数 8 万以上まで応答。
http://groups.google.com/group/comp.lang.functional/msg/33b7a62afb727a4f?dmode=source2000万個のプロセス (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
まずはサンプルとデモ
• 細かいことは気にしない。 • なんとなく雰囲気がわかればよい。 • 後でまた説明します。
42
関数を定義してみる
%% 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)].
43
データとしての関数( 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.
44
いたるところにパターンマッチ%% 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.
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
リアクティブなプロセス
%% 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.
47
なぜか 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; // 問い合わせに応える }}
48
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.