HaxeでPHPの配列を直接利用する場合はphp.NativeArray
というクラスを利用します。
このクラスには[]
を利用して値をセットするのですが、キーの型にスカラータイプが利用出来、また、値はDynamic
となっているためHaxeのコードから利用するには型システムの恩恵をうけることが出来ません。
かと言って、Haxeの用意しいるArray<T>
クラスではトランスパイル時にHaxeの定義する配列クラスでラップされた配列が出力されるため、オーバーヘッドが生じパフォーマンスへの影響を与えてしまいます。
この投稿では、オーバーヘッドを可能な限り抑えて、かつ、Haxeの型システムを利用しながら配列を利用する方法について記述します。
抽象型の定義
Haxeでは型が曖昧な言語とのコミュニケーションのために1、特定の型の抽象を定義する機能があります。
PHPの配列の型はT<K, V>
という型で表現出来ると思います。(K=キー
, V=値
)
Haxeではこれを下記のように定義します。
abstract PHPArray<K, V> {}
抽象型では、ラップする型のことを__基底の型__と呼んだりします。
先程定義したPHPArray<K, V>
はphp.NativeArray
に型情報を持たせるための抽象型ですので、基底の型はphp.NativeArray
となります。
下記のように指定します。
import php.NativeArray;
abstract PHPArray<K, V>(NativeArray) {}
この抽象型のコンストラクタで自身の実際の型を設定してあげれば、php.NativeArray
に型情報を持たせて扱えるようになります。
import php.NativeArray;
abstract PHPArray<K, V>(NativeArray) {
public inline function new() {
this = new NativeArray();
}
}
例えば、HaxeでキーがInt
、値がString
の配列を定義する場合は下記のように書きます。
var arr = new PHPArray<Int, String>();
このコードは下記のように変換されます。
$arr = [];
型の情報はHaxeの中にだけ残り、PHP側では型情報が消えて、ピュアな配列の定義のみが出力されていることが分かります。
メソッドを実装する
さて、ここまでで定義したPHPArray<K, V>
にはなんのメソッドも実装されていないので何の処理も行うことが出来ません。
メソッドを定義して処理をおこなえるようにしていきましょう。
Haxeでは、トランスパイル対象の言語のコードを直接出力する機能があり、untyped __[lang]__(code, args...)
といった形式で記述することで利用できます。
今回の目的はオーバーヘッドレスなので、極力PHPの標準APIを利用したいためこの機能を利用してメソッドを実装していきます。
PHPで言うところのcount(array)
といった処理を行うメソッドlength
を実装してみましょう。
以下のように定義します。
import php.NativeArray;
abstract PHPArray<K, V>(NativeArray) {
public inline function new() {
this = new NativeArray();
}
public inline function length(): Int {
return untyped __php__("count({0})", this);
}
}
このコードはHaxe側からは下記のように利用出来ます。
var arr = new PHPArray<Int, String>();
arr.length();
トランスパイル後には下記のように出力されます。
$arr = [];
count($arr);
特にオーバーヘッド無く利用出来ていることがわかりますね。
メソッドはこのような要領で定義していくことが出来ます。
配列へのキーアクセス
@:arrayAccess
メタデータを利用することで、抽象型に[]
を利用したアクセスが出来るようになります。
これを利用することで、HaxeからもPHPのようにarr[x] = y;
のような記述が可能になります。
下記のように定義します。
import php.NativeArray;
abstract PHPArray<K, V>(NativeArray) {
public inline function new() {
this = new NativeArray();
}
@:arrayAccess public inline function arrayAccess(k: K): V {
return untyped __php__("{0}[{1}]", this, k);
}
@:arrayAccess public inline function arrayWrite(k: K, v: V) {
untyped __php__("{0}[{1}] = {2}", this, k, v);
}
}
このコードはHaxe側からは下記のように利用出来ます。
var arr = new PHPArray<String, String>();
arr["foo"] = "bar";
php.Lib.dump(arr["foo"]);
トランスパイル後には下記のように出力されます。
$arr = [];
$arr["foo"] = "bar";
var_dump($arr["foo"]);
残念ながら、$arr[] = $var;
のような記法はHaxeでは出来ません。
このあたりは、素直にpush
メソッドを実装したほうが無難です。
注意
トランスパイル対象言語のコードを埋め込む箇所は一時的に型の安全性が消えてしまっています。
このような定義を行ったら必ずテストを書いて想定通りに動作していることを確かめないといけません。
まとめ
今回実装したコードは下記のようになります。
import php.NativeArray;
abstract PHPArray<K, V>(NativeArray) {
public inline function new() {
this = new NativeArray();
}
public inline function length(): Int {
return untyped __php__("count({0})", this);
}
@:arrayAccess public inline function arrayAccess(k: K): V {
return untyped __php__("{0}[{1}]", this, k);
}
@:arrayAccess public inline function arrayWrite(k: K, v: V) {
untyped __php__("{0}[{1}] = {2}", this, k, v);
}
}
抽象型を利用することで、型の情報を付与しつつネイティブコードが出力出来ることがわかったと思います。
抽象型には他にもオペレーターを独自に定義する機能などもあるので、可読性を考慮しつつ色々定義してみると楽しいですね!
-
用途はこれだけではないのですが、今回は割愛させて頂きます。 ↩