ocamljs を使おう : OCamlからJavaScriptへの変換

ocamljsOCamlから JavaScript へのトランスレータだ。前回の記事と順番が逆になってしまったけれど今回はocamljsについて書く (基本的に http://jaked.github.com/ocamljs/ 以外のことは書いてないです。)。
JavaScriptは歴史的経緯とその簡潔さからwebブラウザで使われているが,静的型付けでないので信頼性の面でいまいち不安だったり、言語の使いやすさの面でもパターンマッチの構文が無いとか、まともなモジュールシステムがないとかで良くない。 ocamljsを使えば、比較的安全・簡潔に大規模かつ動的なwebページを構成できるかもしれない。
ocamljsは残念ながらまだ成熟しているとはいえないが、実は既にすごく高機能だ。 現バージョンで既に DOM や jQueryOCaml から操作できるし、いざとなれば JavaScriptのソースを OCaml に直接記述できる構文拡張も備わっている。他には lwt (light-weight thread) のライブラリと併用できるので AJAX関連のコードも複数スレッドを用いてスッキリと記述できるだろう。 作者自身が開発している froc (関数的にイベント動作を記述できる Functional Reactive Programmingの実装) や orpc (リモートプロシージャコール) も楽しそうだ。 参考:froc の examples
これまでocamljsを使っているかぎりでは、実行効率が悪いと感じたことはない。 ocamljsは OCamlコンパイラコンパイルの途中で生成するλ式を使って、通常バイナリを生成するところを代わりにJavaScriptのコードを生成する。 このλ式JavaScriptのfunctionに対応する。 効率の良さの理由かもしれない。
類似のプロジェクトに、obrowserという OCamlが吐くバイトコード(.cmoとか)をJavaScriptで解釈実行する 実装がある。でもおそらく ocamljs の方が効率よく実行するのだろう。またocamljsはJavaScriptのオブジェクトをうまくOCamlのオブジェクトに対応づけるのに成功していると思う。 他には Js_of_ocamlという、OCamlバイトコードからJavaScriptを生成するツールがある。まだ試していない。Ocamljsとのパフォーマンスの差はきっとあるはずだ。
静的型付けの言語からJavaScriptのコードを生成するツールは他に Google Web Toolkit がある。余談になるが GWT を使っているアプリ一覧というのを見つけた。 Google CheckoutGWT が使われているのは、やはりクリティカルな部分に静的型付けで信頼性を担保しておきたかったのだろうか?

雑感

OCamlで書けるようになって一番嬉しかったのはバリアント型とパターンマッチが使えることだった。もちろん型検査があるのも嬉しいのだけど。
今は CSS3のアニメーションやスマートフォンのタッチパネルの機能を使って色々書いているのだけれど、JavaScriptで書いていたときはUI周りのイベントハンドリングが複雑になりがちだった。
でも OCaml に切り替えたら、 バリアントで場合分けして、あるケースにのみ必要なデータをタグに押し込めることで色々とスッキリ書けるようになった。 良いです。
面倒なときは JavaScript のコードを直接OCamlに書けばいい。 Obj.magic で強制的に型変換(ライブラリに必要なCSSのプロパティが定義されていないとか)することもあるけれど、ごく稀だと思う。


ここからもっと詳細↓

インストール

  1. OCamlOCamlのライブラリ findlib, ulex をインストール。 Macなら port install ocaml caml-findlib caml-ulex
  2. ocamljs のソースツリーを展開する。
  3. 1.でインストールしたのと同じバージョンのOCamlのソースツリー(例:ocaml-3.12.0.tar.bz2) を ocamljs の兄弟ディレクトリに展開する。
  4. ocamljs のツリーで ./configure && make && sudo make install ドキュメントが必要なら make doc

使ってみる: test.ml を test.js にコンパイル

ソース:test.ml
let _ = Dom.window#alert "Hello, ocamljs!"

alert('Hello, ocamljs'); を実行するだけのプログラム。

コンパイル
ocamlfindjs ocamljs -syntax camlp4o -package jslib.inline,dom test.ml -linkpkg -o test.js

ocamlfindjs は findlib の ocamlfind コマンド と同じ。つまり:

  • ocamlfindjs ocamljs はコンパイラの呼び出し
  • -package dom は Domモジュール を利用するためのおまじない
  • -syntax camlp4o と -package jslib.inline は OCaml のコード中で JavaScript を書くためのおまじない
  • -linkpkg は JavaScript を吐くために必要なオプション

生成された test.js をFirebugなどブラウザのJavaScriptコンソールに貼り付けたり htmlのscriptタグでロードすれば動く。

JavaScriptとの連携

  • 数値型はすべてJavaScriptの数値で解釈される。
  • 文字列の扱いは少し厄介だ。 String.create で作った場合に mutableなOCamlの文字列に, それ以外の場合はJavaScriptのimmutableな文字列になる。 (といっても、mutableな文字列なんて私は使ったことがないのでそれほど問題とは思わない。)
  • OCamlのオブジェクトはJavaScriptのオブジェクトに対応する(後述)。これが ocamljs の最も素敵なところだ
  • OCamlの関数はJavaScriptの関数になる(OCamlの関数をJavaScript側からコールバックできる)。(逆は真ではないような気がする;うろ覚え)
  • OCamlの配列はJavaScriptの配列…だったはず… だけど Javascriptモジュール には js_arrayという別の配列クラスもある…
  • レコードやバリアントはOCamlの中でしか使えない。

基本的なモジュール

PervasivesやPrintfなどOCamlの標準ライブラリに含まれるモジュールは大抵使える。ほかにJavascriptモジュールはJavaScriptの標準ライブラリをラップしている(私は使ったことがなかった…)。
ブラウザに関する殆どの基本的な操作は Domモジュールにある。例えば alert は Dom.window#alert : string -> unit だ。 あと document オブジェクトは Dom.document (つまり document.getElementById は Dom.document#getElementById) この2つがわかれば後はドキュメントを読みつつなんとかなる…かもしれない。
スタイルシートの属性なんかも一通りstyleクラスに揃っているのであまり困らない。
もし足りない属性をみつけたらOcamljs.fieldref : 'a -> string -> 'b でハッシュとして読み出すか, 既存の class type を inherit した新しいclass typeを作り、 Obj.magic でムリヤリ変換する。
ここでJavaScript側からnullかもしれない値を拾ったらOcamljs.option_of_nullable : 'a -> 'a optionでoption型に変えておくとよい

JavaScript のオブジェクトをOCaml側で触るには

メソッド呼び出し

JavaScriptのオブジェクトに対するメソッド呼び出し o.f(arg1,..,argN) は OCaml で o#f arg1 .. argN になる。直感的だ。

値の代入と読み出し

一方、値の設定や呼び出しは少し特殊で、 o.f の読み出しは o#_get_f となり、 代入 o.f = e は o#_set_f e となる。

メソッドの別名と型付けに関して

この他にも ocamljs において メソッド名の後につく _.*_ は無視される。例えば addEventListener と addEventListener_mouseEvent_ は おなじ JavaScript の addEventListener メソッドを呼び出す。 こうやって1つのメソッドに複数の型をうまく付けている(OCamlではメソッドの多重定義はできない)。
DomやjQuery等でどうしてもうまくOCamlの型がつけられない場合は 'a 型を引数に取ったり #element などの #型が戻り値の型になっていたりして型安全ではなくなっている。これは仕方がない。

ほか

ネイティブな(JavaScriptの)メソッド呼び出しの半適用はできない。ひとつでも引数を与えるとその場で呼び出されてしまう。 つまり引数の数をうっかり間違えても普通にメソッドが呼び出されてしまうので少し危険。 ただこうした引数の不足等はOCamlの場合型検査とコンパイラ警告で全て検出できるので、警告を無視したり自分で ignore とかやってしまわない限りは問題ない。 また半適用は例えば2引数のo#fについて o#f a としたいならば (fun x -> o#f a x)とすれば同じことだ。

インラインJavaScript

面倒なら JavaScriptOCaml のコードに直接書けばいい。その場合、

open Ocamljs.Inline

としてから、 << と >> の間に JavaScript の式を書く(ただし正規表現リテラルなど一部使えない構文がある)。 さらに、 JavaScript の式中で OCaml の式を $ 1. +. 0.1 $ のように書けるので、「〜〜が書けない!」ということはまず起こらない。
例えば、あまり役に立たないけれど、alert関数を自前で書くなら

let alert (s:string) = ignore << alert($s$) >>

となる。 ここで << >> で囲んだJavaScript式は 'a 型を持つのでさっさと型を付けてしまったほうがいい。

続きはまた今度