『プログラムErlang』12章、Cの代わりにRubyで書いてみた。極力ベタな移植を心がけたが、随所にRubyらしさを漂わせたつもり。
example1_driver.rb
require './example1'
require './example1_comm'
def main()
begin
loop do
fun, *args = read_cmd()
result = case fun
when 1
twice(args[0])
when 2
sum(args[0], args[1])
end
write_cmd([result]) if result
end
rescue
STDERR.puts("finishied")
exit
end
end
main()
example1_comm.rb
def read_cmd()
len = read_exact(2).unpack('n').first
read_exact(len).unpack('C*')
end
def write_cmd(cmds)
bin = ([cmds.size] + cmds).pack('nC*')
write_exact(bin, bin.size)
end
def read_exact(len)
str = ""
str += STDIN.sysread(len) while str.size < len
str
end
def write_exact(bin, len)
wrote = 0
wrote += STDOUT.syswrite(bin[wrote...len]) while wrote < len
wrote
end
example1.rbはそのまんまなので省略。
RPMForgeをリポジトリに追加しておけば、全てのPerlモジュールをyumでインストールすることができる。
sudo yum --enablerepo=rpmforge install \
perl-Image-Size perl-SOAP-Lite perl-Crypt-DSA perl-XML-Atom \
ImageMagick-perl perl-Archive-Zip perl-IO-Compress-Zlib perl-PerlIO-gzip
#DB接続モジュールはお好みでどれか一つ
sudo yum --enablerepo=rpmforge install perl-DBD-MySQL \
perl-DBD-Pg perl-DBD-SQLite perl-DBD-SQLite2
MTが扱い難い点の一つは、依存するPerlモジュールを揃えるのが大変だという点だ。モジュール同士の依存関係はcpanコマンドで解決できるのだが、全てをコンパイルするにはそれなりの時間がかかるし、ちょっと環境が古いとImageMagickやXML::Atomなどの重要なモジュールがコンパイルできなかったりして、面倒なことこの上ない。まったくyum様々、RPMForge様々だ。
正直、現代的なサイト制作というものを侮っていた。
このブログでWordPressを使ってみて、その扱いやすさに感心したので、MTの代わりにサイト制作用CMSとして使えないかデザイナーに提案してみたところ、あっさりと却下された。
理由は「複数のブログを扱えないから」だそうだ。
詳しく話を聞いていくうちに、彼/彼女らにとって「ブログ」とはサイトを構造化する際の基本単位であり、権限の委譲もコンテンツとデザインの分離も、この単位に沿ってなされるものらしいということが分かってきた。
プログラマとしては、「ブログ」よりももっと根源的な構造があるのではないか、と考えたくなるのだが、多分それは違う。多くのCMSが提示するようなプレーンなツリー構造や、静的サイトに見立てたファイルの比喩では、そこまで役に立たない。あくまでもフル機能を備えた「ブログ」だからこそ意味がある。
- 一定の構造を持つデータ=記事のリストである
- 記事は基本的に投稿日時で分類されるが、必要ならカテゴリ/タグといった追加の属性で分類できる
- 投稿日時や属性に基づく何通りかのビューを標準で備える
- 記事にはメディアファイルを含めることができる
- 記事は、必要ならばフィードバックを受け付けることができる
- デザインには関与しない、記事のみを書く投稿者を複数割り当てることができる
- 以上を管理するための洗練されたWebインターフェイスがある
こういう特徴を備えた構造/システムをなんと呼ぶべきか?やはり「ブログ」としか言い様がないのだ。ちょうどプログラマにとってのRDBMSをイメージすると分かりやすいかも知れない。RDBMSが便利なのは、単にある構造でデータを保存できるからというだけではない。Database/Table/Rowという標準的な構造があり、SQLという標準的なインターフェースがあり、権限管理や様々なツールを含むMS(Management System)の部分があってこそ、プロフェッショナルな道具として役に立つ。
WordPressは、ブログを基本単位として自在に扱う能力を欠いている。いわば個人向けの用途特化型データベースソフトだ。その点では現状、MTの方がずっと優れている。
プログラマとして、プロフェッショナルとして、いろいろと考えさせられる経験だった。

チャットサーバとクライアントのテスト
『プログラミングErlang』11.7に載っている演習の実演。サーバをVMware上のUbuntuで動かし、ホスト側のMac OS Xと合わせて計6つのクライアント・ウィンドウを開いている。グループは2つあり、「Miki」というユーザは両方のグループに(それぞれ別のクライアントとして)ログインしている。
- グループの全員の名前を表示するコードを追加してみよう
- → gsのlistboxオブジェクトを使用して実装した
- すべてのグループの一覧を表示するコードを追加してみよう
- → 同じくgsのlistboxを使用して実装した
- 1対1の会話を追加してみよう
- → メンバー一覧の中からユーザ名を選んでメッセージ送信を行うと、そのユーザに対してのみメッセージが届くようにした(スクリーンショット中「765pro」グループの「Haruka」と「Miki」のやり取り)。また自分を選ぶと自分にだけ表示されるようにした(「Yukiho」のメッセージ)
- グループコントローラをサーバマシンで動かさずに、グループに最初に入ったユーザのところで動かすためのコードを追加してみよう
- → どうしても理解できなかったので実装しなかった
以下雑感。
chat_serverとchat_groupはPidを管理するために微妙に異なるデータ構造を使っている。これが非常にイケてない。DRY原則に従って、lib_pid_assocというモジュールを作って統合した。例えば要素を取り除くremove関数は以下の通り。
remove(Pid, Assoc) ->
remove(Pid, Assoc, []).
remove(_, [], L) -> {"????", L};
remove(Pid, [{Key, Pid}|T], L) -> {Key, reverse(L, T)};
remove(Pid, [H|T], L) -> remove(Pid, T, [H|L]).
gsのlistboxオブジェクトは、一度何かのアイテムを選択してしまうと、マウス操作では非選択状態に戻せなくなるらしい(少なくとも私はその方法を発見できなかった)。さすがに不便なので、Membersラベルをクリックしたら選択を解除するコードを追加しておいた。
loop(W) ->
receive
%...
{gs, label_m, buttonpress, _, _} ->
gs:config(listbox_m, {selection, clear}),
loop(W);
%...
end.
パッと見の印象ほど複雑ではないし、難しくもない(入門書の例なんだから当然か)。いくつか明文化されていない設計方針を読み取れれば、かなり見通しがよくなる。
状態遷移モデル
オートマトンがどうとか理論的なことはよく分からないが、「状態」と「状態遷移」の考え方が使われていることは素朴に理解できる。状態は、全体が receive … end で構成された関数として表現されており、メッセージの受信を引き金として別の関数を呼び出す(=遷移)か、末尾再帰で自分自身を呼び出す(=遷移無し)、もしくは終了する。
状態を表す関数は名前も特徴的である。普通の関数は「動詞+目的語」が多いが、状態の関数は「形容詞または*loop」の形をしている。
この状態遷移モデルについて、本文中では11.3のチャットクライアントの説明の中でわずかに示唆されている。どうせなら状態遷移図でも載せておけばより分かりやすかったのではないかと思うのだが、説明が煩雑になることを避けたのか、あるいは後々OTPを用いる際には不要になる概念なのか……今の段階ではまだ分からない。
オブジェクト指向
状態遷移モデルにおける「状態」の関数は、オブジェクト指向における「オブジェクト」との類似性も併せ持っている。すなわちプロセスがインスタンスであり、ループの引数がインスタンス変数であり、応答するメッセージがメソッドである。io_widgetが特に分かりやすい。
この類似性は、ある意味当たり前ではある。なぜならメッセージ送信モデルはオブジェクト指向の先祖の一つだからだ。
……
状態遷移モデルにせよオブジェクト指向にせよ、様々なプログラミングパラダイムを全てプロセスとメッセージ送信に還元していくのがErlang流、という理解で良いのだろうか。
最後にコードの中に見つけた変なところを挙げておく。
- io_widget:update_state/3 は不要。またio_widget:loopで{updateState}を受ける部分も不要
- chat_group:delete/3 で、lists:reverse/2 の引数が逆(これだと削除するごとにリストが逆順になるので意味がない)
- io_widget:widget/1 内のpackerの設定が変。maxよりminが大きかったり、minとmaxが同じだったり(fixedを使えば済む用途のはず)
どこまで突っ込むべきか、悩ましい。
- lib_chanの実装にどこまで突っ込むか
- GUIプログラミングにどこまで突っ込むか
- 11.7の演習にどこまで付き合うか
lib_chanの詳細は本の末尾、付録Dで解説されている。だから当面は使い方だけ覚えて、実装については後回しで良い……と判断したいところなのだが、そうも言っていられない。11章までに載っている説明では、IRC Liteのコードを理解することはとてもできないからだ。
Erlangの標準GUIライブラリ gs については、公式サイトの『GS User’s Guide』が非常に分かりやすい。頭から順に目を通していって、最後の「Built In Objects」を必要に応じて参照すれば、十分に事足りると思われる。
11.7の演習には、問題の意図がよく分からないものがいくつかある。「1対1の会話を追加してみよう」。これはちょっと曖昧過ぎる。「グループコントローラを、グループに最初に入ったユーザのところで動かそう」。これはグループに最初に入ったユーザのマシンをサーバとして機能させる、という意味なのだろうか?もしそうなら、今とは全く異なるアーキテクチャを考えなければならない気がする……。それとも他に選択肢があるのか?
『プログラミングErlang』11章のIRC Lite。オーム社のサイトからコードを落として実行しても、うまく動かなかった。以下、解決までの道のり。
lib_md5が無いというエラーになる
これは原著のフォーラムで解決策が見つかった。
getting error on starting chat-client: lib_md5 missing?
socket_distでmakeを実行する前に、その一つ上のディレクトリ(日本語版ならjaerlang-code-ja)でmakeを実行しなければならない。
io_widgetのウィンドウが表示されない(Ubuntuの場合)
tk がインストールされていないと動かない。
標準ではtk 8.4がインストールされる。
io_widgetのウィンドウが表示されない(Mac OS Xの場合)
MacPortsでErlangをインストールした場合、自動的にtk 8.5.5がインストールされる。しかし下のようなエラーが出て動かない。
Application initialization failed: couldn't connect to display ":0.0"
Error in startup script: couldn't connect to display ":0.0"
while executing
"load /opt/local/lib/tk8.5/../libtk8.5.dylib Tk"
("package ifneeded Tk 8.5.5" script)
invoked from within
"package require Tk 8.3"
(file "/opt/local/lib/erlang/lib/gs-1.5.9/priv/gstk.tcl" line 7)
いろいろ検索した結果、X11.appを起動していないと動かないということに気付いた(「アプリケーション」→「ユーティリティ」→「X11.app」)。
MacPortsのtkには「quartz: Use native Mac OS X UI instead of X11」というvariantsがあって、これを有効にすればX11を起動しなくても動くかも?と思ったが、駄目だった。
……
いずれも初歩的なことなんだが、解決までにずいぶんと時間がかかってしまった。
githubに登録して、『プログラミングErlang』8章11の練習問題2のコードを載せてみた。初git。
http://github.com/tkyk/erlang-exercise—/tree/master
8章までの知識と覚えたばかりのmakeの知識を総動員して、極力凝った構成にしてある。コードは役割ごとに複数のモジュールに分割して、特にリングを構築する部分は実行時にアルゴリズムを切り替えられるようになっている。いわゆるストラテジーパターン。
次のようにコードを落としてきてmakeを実行すると
git clone git://github.com/tkyk/erlang-exercise---.git
make
erlのコンパイルが行われた後、各アルゴリズムに対応した3つのシェルスクリプトが生成される。いずれも今まで日記に書いてきたもの。
引数としてN(プロセス数)とM(メッセージを回す数)を与えて実行すると、erlが起動されて計算が実行され、実行時間が表示される。また make run とすると3つのスクリプトそれぞれに、何通りかのNとMを与えて自動的に実行する。
$ ./ring_constructor_child.sh 1000 1000
Constructor=ring_constructor_child, N=1000, M=1000
time=0.55, (0.563) seconds
なお、この練習問題では「リング」の定義が曖昧なので、自分なりの判断で以下の条件を課している。
- メッセージの転送を開始できるのは始点となるプロセスのみ
- 始点以外のプロセスは自分の”前”のプロセスを厳密に認識して、他からのメッセージは受け取らない
この条件により実装が複雑になっている部分があるので、条件を外して単純化したバージョンを別ブランチで用意した。
http://github.com/tkyk/erlang-exercise—/tree/ignore_msg_sender
lib_ring.erl以外の内容はmasterブランチと全く同じで、使い方も同じで、実行速度もほとんど同じである。
……
この練習問題にここまでこだわる理由はどこにもないのだが、Erlangに飽きないうちにできるだけのことはやっておきたい。
『プログラミングErlang』8.6で解説されている選択受信について、ちゃんと理解できているか自信がなくなってきたので、下のようなコードを書いて実験した。
loop(X) ->
Y = X + 1,
receive
X ->
io:format("received: ~p, continue.~n", [X]),
loop(Y);
Y ->
io:format("received: ~p, finish.~n", [Y])
end.
receiveのパターンにマッチしなかったメッセージは捨てられるわけではない。プロセスの「メールボックス」に保存されたまま照合の機会を待ち続ける。
9> Pid = spawn(fun() -> lib_misc:loop(1) end).
<0.49.0>
10> Pid ! 1.
received: 1, continue.
1
11> Pid ! 2.
received: 2, continue.
2
12> Pid ! 6.
6
% この時点ではマッチしない
13> Pid ! 3.
received: 3, continue.
3
14> Pid ! 4.
received: 4, continue.
4
received: 6, finish.
% 再び照合が行われ、マッチした!
Ubuntu 8.10にR12B-5をインストール
分散Erlangの実験を行うために、VMWare上のUbuntuに最新版のErlang(R12B-5)をインストールした。aptではR12B-3までしか提供されていなかったので、公式サイトからソースを落としきてコンパイル。Ubuntuインストール直後の状態では以下3つのパッケージが足りていなかった。
- libncurses5-dev
- m4
- sun-java6-jdk
READMEにはJDKはオプションだと書いてあるのに、入れないとコンパイルが完了しなかった。無効にするconfigureオプションも見当たらない。
手順については特筆するようなことはなし。
tar xvzf otp_src_R12B-5.tar.gz
cd otp_src_R12B-5
./configure && make
sudo make install
Mac OS X 10.5 にSMPオプション付きでインストール
OS XではMacPortsを使って一発で最新版がインストールできる。素晴らしい。が、デフォルトではSMPオプションが有効になっていないので、smp variants を指定して入れ直すことにした。SMPオプションがどういうものなのか、まだよく分かってないけど。
sudo port deactivate erlang
sudo port install erlang +smp