heXeを試してみた(jQueryのcssとclickを使ってみる)

haXe(ヘックス)は静的型付け言語で、コンパイルするとJavaScript(とかいろいろな言語)ができるらしい。JSXをいじっていたら「コンパイルするとJavaScriptが出力される静的型付け言語にはhaXeもある」を教えてもらったのでこれも試してみよう。JSXでやった「型のことを全然考えてないjQueryを使ってクリックのハンドラを付けたり色を変えたりする」というのをゴールにする。

インストールする

Download - Haxe

インストーラをダウンロードして起動する。特に問題なくインストール完了。

~$ haxe
haXe Compiler 2.09 - (c)2005-2012 Motion-Twin
 Usage : haxe -main <class> [-swf|-js|-neko|-php|-cpp|-as3] <output> [options]
 Options : ...

IDE

Haxe IDE and Editors - Haxe

ふむふむ、じゃあEmacs用のhaxe-mode.elを…ガーン、404だ。しかたがないのでjsx-modeを使う(ぇ

Hello!

$ cat Hello.hx
class Hello {
      static function main() {
             trace("Hello!");
      }
}
$ haxe -js hello.js -main Hello Hello.hx
$ wc hello.js
     309    1142    8140 hello.js

コンパイル成功、JSファイルが出力された。Helloに関する部分だけ見てみよう。

var Hello = $hxClasses["Hello"] = function() { }
Hello.__name__ = ["Hello"];
Hello.main = function() {
        haxe.Log.trace("Hello!",{ fileName : "Hello.hx", lineNumber : 3, className : "Hello", methodName : "main"});
}
Hello.prototype = {
        __class__: Hello
}

HTMLからhello.jsをscriptタグで呼んだら、無事コンソールにファイル名、行数とHelloが出力された。

インストールされた場所

Macの場合、何も設定しないと/usr/lib/haxe/に入ってるみたいね。ライブラリはそれのstd/...に入っている。
jQueryはサポートされてて /usr/lib/haxe/std/js/JQuery.hx とかがあるけど、それを単に使ったのでは他のサードパーティ製ライブラリを使う際に困ると思うので今回はあえてこれを使わずにjQueryを呼び出してみようと思う。

まずはJQuery.hxを読む。

typedef JqEvent = {
	var target : Dom.HtmlDom;
	var currentTarget : Dom.HtmlDom;
	var relatedTarget : Dom.HtmlDom;
...
	var which : Int;

	// propagation
	function isDefaultPrevented() : Bool;
...
}

ふむふむ、複雑なメンバを持ったマッピング型にtypedefでわかりやすい名前をつけることとができる、と。

extern class JQuery implements ArrayAccess<Dom.HtmlDom> {

「implements ArrayAccess」はいいな。jQueryオブジェクトがArray状のオブジェクトであることをそうやって表現するのか。

	@:overload(function(j:js.JQuery):Void{})
	@:overload(function(j:js.Dom.Window):Void{})
	@:overload(function(j:js.Dom.HtmlDom):Void{})
	function new( html : String ) : Void;

コンストラクタが文字列と関数の両方を取りうることをオーバーロードで表現している。なるほど、JSXでも複数の型を取りうる関数はその型の種類の数だけオーバーロードすればいいんだな。

	function removeClass( ?className : String ) : JQuery;

省略できる変数はこうやって表現するのか。

	private static function __init__() : Void untyped {
		#if !noEmbedJS
		haxe.macro.Tools.includeFile("js/jquery-latest.min.js");
		#end
		var q : Dynamic = window.jQuery;
		js.JQuery = q;
                ...
        }

ふむふむ。

とりあえずjQueryオブジェクトの表示

じゃあとりあえずjQueryオブジェクトを取得して表示させてみよう、と思って下のようなコードを書いたが

class Main {
    static function main() {
        haxe.macro.Tools.includeFile("js/jquery-latest.min.js");
	var q : Dynamic = window.jQuery;
	trace(q);
    }
}

windowなんて知らないぞ、と怒られる。

$ haxe -js main.js -main Main Main.hx
Main.hx:4: characters 19-32 : Unknown identifier : window

あれ?JQuery.hxはどこからwindowを持ってきてるんだ?

std$ grep window **/*.hx
...
js/Lib.hx:	public static var window : Window;
js/Lib.hx:		if( untyped __js__("typeof window") != "undefined" ) {
js/Lib.hx:			window = untyped __js__("window");

なるほどなるほど、理解した。状況をシンプルにするために今回はJSXと同じようにHTML側でjQueryをロードしよう。haxe.macro.Tools.includeFile("...")の使い方はまた今度調べる。

そうしたら、JSXの時にやったのと同じように無事jQueryオブジェクトが表示できた。めでたしめでたし。

class Main {
    static function main() {
        trace(untyped __js__("window"));
        // -> Main.hx:9: [object Window] main.js:85
        trace(untyped __js__("window.jQuery"));
        // -> Main.hx:10: <function>
        untyped { window.console.log(jQuery); }
        // -> function (a,b){return new e.fn.init(a,b,h)}
    }
}

.cssと.clickを呼ぶ

じゃあ次は.cssとか.clickを呼んでみよう。

前回JSXで試した時は結構苦労したので身構えていたけど、拍子抜けするほどあっさりできた。__js__がJavaScriptの式をそのまま実行する抜け道で、Dynamicが「この変数には型チェックをしない」という抜け道。DynamicをJSXのvariantみたいなものだと思っていたので最初はjQuery("p")...の部分をuntypedで囲う必要があると思ったがjQueryがDynamicだと必要無いようだ。

class Main {
    static function run() {
        var jQuery : Dynamic = untyped __js__("window.jQuery");
        jQuery("p").css("background", "#afa");
        jQuery("p").click(function(e){
            e.target.innerHTML += "hoge";
        });
    }
    ...
}

つまり、haXeだとちょっと変数をDynamicって宣言するだけで、それにまつわる式が全部JavaScriptレベルの型安全性に落ちるようだ。これを「型安全性が失われた!悲劇だ!」と思うか「型宣言を書きまくらなくてもすぐ移行できる!やった!」と思うかはケースバイケースだね。僕は既存のJSのコードを完全な型安全にするには現実的でないコストがかかると思っているので、heXeの移行コストが低そうなのは魅力的に見えるな。

今回のソースコードはこちら https://github.com/nishio/learning_haxe/tree/42efd58c7c84f1f497e76dac7a91ade664c2d3f1/use_jquery