プログラミング言語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
© Copyright 2024 ExpyDoc