JavaScriptで何はともあれ書くコードに、window.addEventListenerがありますよね。
window.addEventListener("load", function(){
console.log("loaded");
});
DOMが初期化されるとコールバックが発火され、DOMがJavaScriptから読み書き可能になります。
jQueryを使う場合はこう。
$(function(){
console.log("loaded");
});
(正確にはjQueryが監視するイベントはloadではなくDOMContentLoadedですが)
関数型言語のアプローチで書き比べてみる
なぜ関数型AltJSで比較するか、それは、個人的な言語設計的な興味と、実用を志向するとどうなるのかが同時にわかっておいしい。関数型言語、副作用を意識してないのと、JSに型がないのでゆるふわな状態をどう管理するかが肝になります。
(僕のAltJSに対する興味は実用JavaScriptの為でもありますが、言語設計的な観点でもあるので)
比較対象
clojure-script
ClojureのAltJS。他の言語のとの大きな違いは、Clojureプロジェクトの一環として開発されており、そこそこ枯れています。
wisp
個人的に昔から気になってたLispのAltJSで、最近使ってみたらかわいかった。
ClojureScriptと比べてコンパイル速度が高速なのと、JSとほぼ一対一に変換される可読性の高さがウリです。マクロも使えます。
wispifyというbrowserifyのtransformもあるので、こっそりプロジェクトに混ぜて使えますね!
js_of_ocaml
ocaml to js。ocaml界では結構使われているそうです。僕は全然使いこなせてませんが。
ocamlのバイナリ一歩手前の中間表現からJavaScriptに落としているそうです。
書き比べ!
JavaScript
var onload = function(){
console.log("loaded");
};
window.addEventListener("load", onload);
関数定義部分と、呼び出し部分をわけて見たいので、この定義をなぞることにします。
ClojureScript
(defn onload []
(.log js/console "loaded"))
(.addEventListener js/window "load" onload)
js/
の名前空間はほぼwindowに対応していると思って大丈夫です。
Wisp
(defn onload []
(console.log "loaded"))
(window.addEventListener "load" onload)
js名前空間がなく、シンボルとして .
を含んだものを指定可能です。これは変換後のJavaScriptと対応している点でも、非常にわかりやすい。S式最高ですね。
js_of_ocaml
公式の例をいじってみるととこんな感じでした。
let onload ev =
Js.Unsafe.eval_string "
console.log(\"loaded\");
";
Js._true
let () = ignore begin
Dom_html.addEventListener
Dom_html.window
Dom_html.Event.load
(Dom_html.handler onload)
Js._true
end
組み込み用にラップされたものばっかりで、実体がよく見えないですね。無理やりUnsafeなオブジェクトで呼んでみましょう。
let () =
ignore (Js.Unsafe.variable "window")##addEventListener((Js.string "load"), (Js.wrap_callback onload))
型が厳しい世界で無理に関数を呼び出してるのでignoreとかUnsafeが飛び交う、非常に危険そうにみえるコードです。まぁ、型が厳しい言語からみたら生のJSはこれぐらい危ないことをしているんだよって示唆でしょうが。
JavaScriptオブジェクトへの関数コールは、引数一つのタプルであることに注意してください。(これで今日三時間ぐらいハマってました… 普通に引数として渡そうとすると、部分適用しようとして落ちます)
比較表
javascript | clojure-script | wisp | js_of_ocaml | |
---|---|---|---|---|
グローバル名前空間 | window | js/window | window | Dom_html.window |
関数呼び出し | console.log(...) | (.log console ...) | (console.log ...) | Dom_html.window##console##log((...)) |
メンバーアクセス | obj.name | (.-name obj) | obj.name | obj##name |
メンバー書き換え | obj.name = 'value' | (set! (.-name obj) "value") | (set! obj.name "value") | obj##name <- "value" |
JavaScript側は言語仕様がそこまで複雑なわけではないので、これだけ知ってればあとはその言語の世界で完結できるんじゃないでしょうか。
僕はJS固有ドメインよりその言語のドメインで表現するのに苦労することが多いです。