Haxeでjavascriptを書いている時にちょっと困ったことがありました。
困ったこと
HaxeではDOMエレメントを厳密な型に分けて管理しています。
例えば、inputタグならInputElement
とかformタグならFormElement
とか...
これらの型は基底となるElement
を継承することで表現しています、しっかりタイプ分けされていて気持ちいのですが...
以下のようなコードをjavascriptの感覚で書けないのです。
<input type="text" id="foo" value="foo">
var element = document.getElementById("foo");
console.log(element.value); // -> foo javascriptならOKだ!!
class Main {
function foo() {
var element = Browser.document.getElementById("foo");
trace(element.value); // -> js.html.Element has no field value Haxeはコンパイルエラー...
}
}
Browser.document.getElementById
はElement
型を返すので、
取得したDOMオブジェクトをInputElement
として扱うことは出来ません。(Element
型はvalue
プロパティを持っていない)
当然解決策は用意されていますが...
untyped
キーワードを利用することでコンパイルチェックを外すことが可能です。
或いは、代入する変数の型をDynamic
にしておくことでも対処可能です。
しかし、いづれの方法もHaxeの型システムを放棄するものですので、あまり利用したくはありません。
class Main {
function foo() {
var element1 = Browser.document.getElementById("foo");
trace(untyped element.value); // -> foo
var element2:Dynamic = Browser.document.getElementById("foo");
trace(element.value); // -> foo
}
}
cast
関数を利用することで、異なる型に変換することが可能です。
cast
を利用すればほぼ解決出来るのですが、どんな型にでも変換出来てしまうという危険性も備えています。
class Main {
function foo() {
var element1 = cast( Browser.document.getElementById("foo"), InputElement );
trace(element.value); // -> foo
var element2 = cast( Browser.document.getElementById("foo"), NodeList );
trace(element.value); // -> js.html.NodeList has no field value
}
}
変換可能な型を限定するためのCastクラスを作って解決する
Haxeの機能にcast
可能な型を限定しておける機能があるかはよく分からなかったので、
独自にcastする処理を書いて解決してみました。
class FromElement {
var element: Element;
public function new(element: Element) {
this.element = element;
}
public function toInputElement(): InputElement {
return cast( this.element, InputElement );
}
}
import FromElement;
class Main {
function foo() {
var element = Browser.document.getElementById("foo");
trace(new FromElement(element).toInputElement().value); // -> foo
}
}
castするクラスを利用することでcast可能な型を限定出来るようになり型安全なcastが可能になりましたが、
記述がやや冗長になります。
(From To の関係がコードから分かるので、この冗長さはある意味綺麗なものだとも思いますが...)
抽象型を作って暗黙的に型をcastする
Haxe3からは抽象型という機能が追加され、型の定義を柔軟に設定出来るようになりました。
Element
型をcastするための抽象型を定義します。
abstract CastElement(Element) {
public function new(element: Element) {
this = element;
}
@:to public function toInputElement(): InputElement {
return cast(this, InputElement);
}
}
import CastElement;
class Main {
function foo() {
var element: InputElement = new CastElement( Browser.document.getElementById("foo") );
trace(element.value); // -> foo
}
}
抽象型を利用すると、代入先の型へのキャストが可能であればその型に暗黙的に変換して代入してくれます。
castクラスを定義して利用するよりコードがスッキリ...したような気もします。
まとめ
まとめではないけど、もっといい方法ご存じの方がいらっしゃれば是非教えていただきたい。