JavaScriptのゲームをjs_of_ocamlでOCamlに移植しようとしてみた
こちらの記事↓を読んで面白そうだったので、前作ったPhantasmal IslandのJavaScriptクライアントを移植しようとしてみた。
ウェブブラウザで関数型プログラミング! js_of_ocaml - Oh, you `re no (fun _ → more)
移植元のクライアントとソースはこちら。
JSPhi (Phantasmal Island Client)
napthats / JSPhi
で、JavaScriptファイルのうちjsphi.jsをjs_of_ocamlに移植してみたのがこれ(適当に書いてるところが多いので注意。一応動く……はず)
移植はわりと機械的にできるかなーと思ったけどそうでもなかった。js_of_ocaml自体もちょっと難しいし、「え、このJavaScriptのコード、OCamlだとどう書くの……?」というのも結構あった(これは多分OCaml慣れてないからもあるけど)。あとミスした時のエラーメッセージが分かりにくい(特にOCamlでは通ったけどJavaScriptでエラーが出るとき)。場合によっては最初から作り直す方がいいかもしれない。
以下は移植してる時にハマったところとか。まだまだ他にも詰まりそうだけど……。
関数の引数の数が定義と合わないときの挙動
JavaScriptで定義より少ない数の引数で関数を呼び出すと、残りの引数はundefinedになる。例えば、
function f(a,b) {alert(a); alert(b);} f("hi");
これを実行すると、hiとundefinedが順に表示される。これをjs_of_ocamlでこう書いてしまうと、
let f a b = Dom_html.window##alert(a); Dom_html.window##alert(b) let _ = f (Js.string "hi")
何も表示されない。何故なら二行目は部分適用(?)とみなされて以下のような感じに変換される(たぶん)から。
function f(a,b) {alert(a); alert(b);} (funtion(b) {f("hi", b);})
fが外部のJavaScriptライブラリの関数だったりしても似たようなことが起こる。例えばJQueryを使って
$(document).ready( ...
と書いてあるとすると、
let jq = Unsafe.variable "$" let _ = Unsafe.meth_call (jq Dom_html.document) "ready" ...
というふうに変換できそうだけど、$は実は2引数関数なので変なエラーが出る。
こういう風にJavaScriptで引数の数が合わない使い方してる場合は、Unsafe.fun_callを使うと上手くいく。
let jq = Unsafe.variable "$" let _ = Unsafe.meth_call (Unsafe.fun_call jq [| Unsafe.inject Dom_html.document |]) "ready" ...
循環参照で困ったらrefでごまかす
例えば、単純に書くと以下のようになってしまう場合、
(* createAとかcreateBとかは外部JavaScriptライブラリの関数 *) let componentA = createA componentB let componentB = createB (fun msg -> componentA##do(msg))
こう書き直す。
let componentA = ref (Unsafe.variable "0") let componentB = createB (fun msg -> (!componentA)##do(msg)) let _ = componentA := createA componentB
createBの中で渡した関数呼ばれたら死ぬけど、その状況はJavaScriptでも同じだったはずなので、多分問題にならないはず。
OCamlの型とJavaScriptの型をごっちゃにしない
関数の引数の型はなるべくOCamlの型にする(例えばJs.js_stringじゃなくてstringを使う)ようにした方がいいと思うけど、JavaScriptから呼ばれる可能性のある関数の引数はJavaScriptの型にしておかないとエラーになる。例えば、JavaScriptのライブラリにコールバックとして関数渡す場合とか。
オブジェクトobjの無引数メソッド(OCaml上ではunitが引数のmeth)fを呼ぶときはobj##f()じゃなくてobj##f(())
書いてある通り。よく考えたら当たり前だけど……。
Hashtblは使えない
と、思う。のでassoc listで我慢。