読者です 読者をやめる 読者になる 読者になる

タオルケット体操

サツバツいんたーねっと

Haxeでメタクラスを使う

教育機関によるプログラミング教育を受けていないので、どちらかといえばJavaC++のような言語よりもSmallTalk属のオブジェクト指向Ruby, Python, Objective-Cです)に馴染みがあり、時々どうしてもクラスそのものをオブジェクトとして扱いたくなってしまいます。

HaxeのことをC#OCamlだと捉えていたので、(そもそもOCamlオブジェクト指向がどういったシステムなのかは知らないが)メタクラス的な機構は存在しないと思い込んでいました。
が、そこは流石マクロてんこ盛りのメタプロし放題の変態言語だけあって普通に使えました。しかもJavaのリフレクションみたいにややこしい書き方をする必要もないし、補完や型推論も普通に動きます。

Type.createInstance(HogeClass, [arg1, arg2, ...]);
// same as new HogeClass(arg1, arg2);

これだけです。超楽ですね。Typeは最初から名前空間に存在する方のモジュールで、haxe.macro.Typeとは別のモジュールであることに注意してください。
他にもcreateEnumなどもメソッドを持ちます。詳しくは http://api.haxe.org/Type.html を参照してください。

さて、インスタンス化のやり方はわかったら今度はクラスオブジェクトそのものの型宣言です。

var cls: Class<HogeClass> = HogeClass;
Type.createInstance(cls, [x, y, z]);
// using Type; すれば cls.createInstance([x, y, z]); とも書ける

Class<T>を使えば良いわけですね。簡単です。

こんなことをして何が嬉しいんだと思われる向きもあるとおもうので、無理矢理ユースケースを考えます。

using Type;

interface IDisplayable {
  public function show(): Void;
  public function hide(): Void;
}

class DisplayableHelper {
  public static function displayWhileFunction<T: IDisplayable>(cls: Class<T>, prepare: T -> Void, func: Void -> Void): Void {
    var disp = cls.createInstance([]);
    prepare(disp);
    disp.show();
    func();
    disp.hide();
  }
}

解説です。

例えばダイアログ的なモノを作るとして、派生クラスが色々あるとします。
派生がゴチャゴチャとある中で、「処理中だけなんかいい感じに表示したい」みたいな共通処理的なコードを書きたい気持ちになってください。しかも、その処理はインスタンスメソッドではなく、スタティックメソッドにしたいのです。どうでしょう、なれましたか?

HaxeにおいてはInterfadeにスタティックメソッドを宣言することは出来ません。なんかVB.NETとかいう言語はInterfaceにスタティックメソッドを宣言、実装することが出来た気がするのですが多分気のせいです。

勿論、Interfaceを継承させる先で、どこか適当なところに抽象基底クラスを作れば良いわけですが、継承というのは煩雑な状態管理などで余計な複雑さを生み出す元凶なのでこの程度のことであっても使いたくありません。そこでメタクラスです。

public static function displayWhileFunction<T: IDisplayable>(cls: Class<T>, prepare: T -> Void, func: Void -> Void): Void
は、まずジェネリックな型変数TIDisplayableで制限しています。
clsはnewしたいクラスそのものです。
さらに、汎用性を高めるためにnewした後のオブジェクトそのものの前準備をラムダ式で渡せるようにprepareを宣言します。Tの型はIDisplayableで束縛されているため、当然のようにprepareで渡すラムダ式内ではちゃんと補完も型推論も効きます。本筋とは関係ないですが、型変数Tを使っているおかげで、例えばbeforeみたいな引数を増やしたくなっても、IDisplayable -> Voidみたいに書かずにT -> Voidみたく書けるのはdryで良いですね。ジェネリクスって素晴らしいですね。

あとはDisplayableHelperをmixinするだけで継承先全てのクラス、例えばclass MessageDisplay implements IDisplayable {}などにdisplayWhileFunctionというスタティックメソッドが生えます(生えたように見えます)。

いじょう、だいたいそんな感じです。

便利昨日っぽい雰囲気を出してはいますが、もちろんお察しの通りcreateInstanceは変数初期化時のコンパイル時検査をすり抜けてランタイムエラーを起こしたりなんだりとHaxeの型安全な良さを損なう機能です。
損なう機能ではあるんですけど、「色々な言語に変換出来る」というHaxeの性格上、問題解決の手駒としてメタプロや黒魔術の知識は多ければ多いほど良いわけですね。使いどころがないには越したことがないのですけれども、現実の作業はどうしても泥臭いです。

おしまい!