2011年07月25日

Hello, javascript

あなたはホームページを運用するうちに、ページ来訪者と大いに語らう場を作りたくなりました。そこで、ブラウザ上で動くチャットルームを作ることにします。チャットルームには発言者の名前と、最新から20個の発言が順に表示されます。
まずはデータの格納先を定義しましょう。ユーザーの名前と、投稿テキストを格納する先を作ります。

// chatは投稿者名とテキストからなる
db /chat : {name: string; text: string}


Opaのデータベースは、実はデータベースに格納された過去値をすべて覚えています。そのため、これだけで「最新のn件」が取得可能です。この辺の仕様は誤って上書きしたデータを簡単に復元できるなど便利なことがある反面、データ容量や個人情報保護などの観点から問題が起きることもありますので、運用の際には注意してください。

  recents = Db.history(@/chat, 0, -num_print)


これで最新のnum_print件が取得できます。

ではまず、これを使い、チャットの表示更新のところを書いてみましょう。まず、得られたrecentsをxhtmlに変換します。

update_chat() = 
// 最新の20件を取得
recents = Db.history(@/chat, 0, -num_print)
// 発言をxhtmlに変換
lines = List.map((x -> <div class="line">
<
div class="user">{x.name}:</div>
<div class="text">{x.text}</div>
</div>), recents)
// 表示!
Dom.transform([#chat <- lines])


あとはDom.transformで<div id=#chat>に書き出すだけです。

「え、この処理はどこで実行されるの?」と疑問に思う方もいるかと思います。答えは、ブラウザとwebサーバー両方です。Dom.transformなどの処理はブラウザでjavascriptとして実行され、Dbアクセスはwebサーバーで実行されます。Dbアクセスはブラウザ側のjavascriptからRPCとして呼び出され、実行されます。プログラマはこのRPCを個別に書く必要が全くありません! これはOpaの非常によい特性であると思います。この機能を使う際にはCSRFに注意してください。見知らぬ人に「こんにちはこんにちは!」などと書かれることのありませぬよう。

最後に、ページを表示し、ボタンを押したら発言を更新するようにしましょう。これでとりあえず、手動更新のチャットの完成です。

// chatは投稿者名とテキストからなる
db /chat : {name: string; text: string}

num_print = 20

update_chat() =
// 最新の20件を取得
recents = Db.history(@/chat, 0, -num_print)
// 発言をxhtmlに変換
lines = List.map((x -> <div class="line">
<
div class="user">{x.name}:</div>
<div class="text">{x.text}</div>
</div>), recents)
// 表示!
Dom.transform([#chat <- lines])

submit_chat() = (
name = Dom.get_value(#name)
text = Dom.get_value(#text)
// textが空でなければ発言
do (
if text != "" then
do /chat <- {~name; ~text}
Dom.clear_value(#text))
// textの中身にかかわらず更新はする
update_chat()
)

show_page() =
<div>
<
input id=#name />
<
input id=#text onnewline={_ -> submit_chat()}/>
<
button type="button" onclick={_ -> submit_chat()}>投稿・更新</>
</>
<div id=#chat onready={_ -> update_chat()}></>

server = Server.one_page_bundle("Chat",
[@static_resource_directory("resources")],
["resources/css.css"], show_page)


(次回に続く)
posted by chun at 02:30| OPA

2011年07月19日

Hello, CSS

こうしてできたページを見てみると、微妙にweb0.9感が漂い、web1.0には何かが足りないことに気づきます。いったい何が不足しているのでしょう?

そうだ、web1.0時代はフォントの色やサイズを変えて遊ぶのが流行っていたのでした。黒背景に白文字・センタリング・フォントサイズ+4。早速これを実装してみましょう。

Opaでは、cssなどのファイルも、できるだけ実行ファイルに含めるような設計になっています。これは、scpなどで1ファイルだけコピーすれば設置が終わるようにという配慮です。細かいことですが、大量のサーバーに配置する際も設置に失敗して404ということが無くなりますし、テストサーバーから本番系に移設する際にも移行途中でのミスは起きません。代わりに、設置時にcssを書き上げておかねばならず、変更に伴い再コンパイルが必要になります。もちろん、開発中のことも考え、cssをon-the-flyで記述したり、ディスクから読むようにしたり、といったことも可能になっています。

server = 
Server.one_page_bundle("Hello",
[@static_resource_directory("resources")],
["resources/css.css"], hello)


/*
resources/css.css
*/
body {
background-color: #000000;
color: #FFFFFF;
}

p {
text-align: center;
font-size: 32pt;
}


無事にソウルフルなページになりましたでしょうか? これで<marquee>タグあたりを付けるとさらにweb1.0っぽくなりますが、悪ノリはとりあえずこの程度にとどめておきます。

#ところで上のCSSを適用すると、Opera11.50でタブ全体ではなく文章の部分だけが黒背景になるのですが、この原因をご存じの方はいらっしゃいませんでしょうか……?

(次回に続く)
posted by chun at 00:48| OPA

2011年07月14日

Hello, transaction

さて今回初めてデータベースを使ったわけですが、このカウンタには問題があります。ユーザーがきわめて多くなると、並列実行の問題から正常にカウントアップされないかもしれません!
このページが1000ページビュー/秒を達成したときに備え、トランザクションを用いてプログラムを書き直しましょう。

トランザクションを用いるには、2つの操作が必要です。
・DBに名前を付ける
Db.transactionを用いてDBにアクセスする

DBに名前を付けるには、database文を使います。
database counter = @local("./hogehoge")
この場合、webサーバーを実行した際のカレントディレクトリ上に、hogehoge_から始まる一連のファイルを作成し、データベースをそこに保存する、という意味になります。

トランザクションを用いるには、トランザクション処理の開始から終了までをまとめた関数を作る必要があります。以前のincr_countをそのままリネームしてatomic_incrとしてしまいましょう。後はDb.transactionにDB名と、トランザクション処理関数を与えるだけです。
  result = Db.transaction(counter, atomic_incr);
トランザクションの実行が成功すると、実行結果が{ some=結果 }という値になって返ってきます。一方、トランザクションの実行途中に別のプロセスがDBの同じ値を書き換えてしまった場合、トランザクションの実行は失敗し{ none }という値が返ってきます。この値を取り出すには、match文を使います。
  match result with
| { none } -> incr_count() // トランザクションをやり直す
| { some = result } -> result

なお、{ some = result } -> result なんて書くのはかったるい! という人のために、~{some} -> someという構文が用意されています。~{some}{ some = some }の糖衣構文です。

これで、あなたのカウンタの1000ページビュー/秒対応が完了しました。あとは中身を充実させるだけですね?

database counter = @local("./counter") // DBに名前を付ける
db /counter/cnt : int // DBの"/counter/cnt"はint型

incr_count() =
(
result = Db.transaction(counter, atomic_incr);
match result with
| { none } -> incr_count() // トランザクションをやり直す
| { some = result } -> result
)

atomic_incr() =
(
c = /counter/cnt;
do /counter/cnt <- c + 1;
c + 1
)

hello() =
<p>
あなたは{
incr_count()}番目のお客様です。
<
/>

server =
Server.one_page_server("Hello", -> hello())


(次回に続く)
posted by chun at 00:16| OPA

2011年07月11日

Hello, counter

Web1.0時代は、「あなたは○○人目の来訪者です」というカウンタをつけるのが流行っていました。
このチープ感あふれる仕組みを含むページを作ってみましょう。

OPAには組み込みのデータベースがあり、dbの後に続けてパスを指定することでデータベースの構造を定義できます。データベースには「型」を指定する必要があります。カウンタはどう考えても整数ですから、整数を表すint型を指定しましょう。

incr_countは、データベースの値を取ってきて、1増やした値をデータベースに入れ、増やした後の値を返す手続き(opaではこれを関数と呼びます)です。あとは、incr_count()を呼ぶたびカウンタ増えるよぽぽぽぽ〜んという寸法です。簡単ですね?

データベースの初期値はどうなるか、気になる人も居ると思います。データベースの初期値は、型によって決まります。intの場合は0が入りますので、このdbの値は0からスタートします。1増やした後の値を返していたのはこういう理由からです。

あとは、ソースファイルをコンパイルすれば、サーバーができあがります。繰り返しますがopaのデータベースは組み込みですので、ライブラリを持って行ったり、DBの設定をする必要はありません。

この実装を見て「あれ、ロックは?」と思った方。ごもっともです。次の回で詳しく説明します。


db /counter : int

incr_count() = (
c = /counter
do /counter <- c + 1;
c
)

hello() =
<p>
あなたは{
incr_count()}番目のお客様です。
<
/>

server =
Server.one_page_server("Hello", -> hello())


(次回に続く)
posted by chun at 23:32| OPA

Hello, OPA

プログラミング言語を遊ぶ以上、まずは何はともあれHello, Worldです。OPAで書かれたプログラムはコンパイルされ、単体でサーバーとして動作する実行プログラムを出力します。

// hello.opa
hello() =
<h1>
Hello, World!
<
/>

server =
Server.one_page_server("Hello", -> hello())


このファイルをhello.opaとして保存し、
$ opa hello.opa
$ ./hello.exe

とするとlocalhost:8080にwebサーバーが起動します。ページが見れましたでしょうか。Web0.1時代の開幕です。

(次回に続く)
posted by chun at 23:17| OPA

Web1.0で遊ぶOPA

OPAをご存じでしょうか? OPAはMLstateという会社が開発したweb開発用の言語です。
わたしはこのOPAがかなり気に入ってしまったので、これから数回にわたり、ちまちまと紹介記事を書こうかと思います。
といっても、わたしはあまりwebページの作成経験がないので、どちらかというとWeb 1.0的なテーマをネタに、サンプルを作ることになりそうですが。
わたしの思う、OPAの良いところリストは以下の通りです。
・パターンマッチ
・パターンマッチ
・パターンマッチ(大事なことなので3回言いました)
・代数型と、それと親和性の良いDB
・型検査(+推論)
・サーバー側とクライアント側を同一の言語で書ける
これからのシリーズで、この特徴をいくつか見ていければ良いかなと思います。よろしくお願いします。

posted by chun at 22:59| OPA