> makibishi throw|

Haxe言語メモ

Haxe 言語 を使うことになりそうなので、チートシートとして使えるように簡単に文法などをまとめておく。(Haxe 4.1.4 時点)

言語の特徴

  • 静的型付言語
  • ActionScript3.0 や c#に似た文法

対応プラットフォーム

  • 独自の仮想マシン(Neko,HashLink)のバイトコードにコンパイルできる他、 JavaScript、Lua など他言語のソースコードにも変換可能。仮想マシンは Windows,macOS,Linux,iOS,Android,Nintendo Switch,PlayStation 4 などのプラットフォームを想定しており、マルチプラットフォーム開発に向く。Haxe Foundationのサポーターなどもほとんどがゲーム会社である。
  • 言語や仮想マシンの開発コミュニティの規模はそれほど大きいわけではないが、 Dead CellsDefender’s Quest など、すでに著名なゲームのマルチプラットフォーム開発で成功を修めている。

変数の型

型は自動で推論されるので省略可能。

プリミティブな型

var integer:Int = 3; // 整数
var float:Float = 3.5; // 浮動小数点数。64ビットなので他の言語のdouble相当。
var string:String = "Hello"; // 文字列
var boolean:Bool = true; // ブール値

// Void型は何も返さない関数の返り値。変数に代入出来ない

配列

var intArray:Array<Int> = [1, 2, 3];
var boolArray:Array<Bool> = [true, true, false];

連想配列(map)

var map:haxe.ds.Map<Int, Int> = [1 => 101, 2 => 102, 3 => 103];

構造体(オブジェクト)

var book:{title:String, page:Int} = {title: "NeverEndingStory", page: 624};
var user:{age:Int, name:String, friends:Array<String>} = {age: 10, name: "John", friends: ["Smith"]};

列挙型 Enum

列挙型の列挙子の中にさらに型を含めることもできる。

enum Color {
	Red;
	Green;
	Blue;
	Rgb(r:Int, g:Int, b:Int);
}

...

var red:Color = Color.Red;
var rgb:Color = Color.Rgb(150, 30, 80);

関数

関数も第一級オブジェクト。(引数) -> 返り値

var traceInt:Int->Void = (x) -> {
  trace(x);
};
var isOdd:Int->Bool = (x) -> {
  x % 2 == 1;
};
var sum:(Int, Int) -> Int = (a, b) -> {
	a + b;
}

Any,Dynamic

どんな値も入る型。なるべく避けるべき。 Dynamic の場合はフィールドアクセスやオペレータの適用も可能だが、Any の場合はキャストしないとエラーになるので、Any の方がより安全。

var anyar:Any = 0;
anyvar = "aa";
anyvar = false;
anyvar = {title: "NeverEndingStory", page: 624};

var dynvar:Dynamic = 0;
dynvar = "aa";
dynvar = false;
dynvar = {title: "NeverEndingStory", page: 624};

trace(anyvar.title); // エラー
trace(dynvar.title); // 通る

trace(anyvar + 1); // エラー
trace(dynvar + 1); // 通る

null について

Haxe 言語自体は nullable だが、ビルドターゲットによっては null が invalid な値となってしまうため、そのようなターゲットを選んだ場合は null を代入するような操作はエラーとなる。そのような場合はNull<T>型を使う。

var nullInt:Null<Int> = null; // static targetでは0
var nullFloat:Null<Float> = null; // static targetではNaN
var nullBool:Null<Bool> = null; // static targetではfalse

なお、ターゲットに関わらず常に null 安全で評価したい場合は@:nullSafetyを使う。


class Main {
  ...
}

変数宣言 final,var

変数宣言のキーワードは final,var の 2 つ。

var a = 0;
a = 1;

final b = 0;
b = 0; // コンパイルエラー

宣言だけを行うことができるが、初期化前に使うとエラー。

var a:Int;
trace(a); // コンパイルエラー

制御文

if 文

var x = 3;
if (x == 3) {...} else if (x == 4) {...} else {...};

for 文

// for文(範囲)
for (i in 0...10) trace(i);

// for文(各要素)
var numbers = [1, 2, 3];
for (i in numbers) trace(i);

// for文(map)
var map = [1 => 101, 2 => 102, 3 => 103];
for (key => value in map) {
  trace(key, value);
}

while 文

var i = 0;
while (i < 100) {
  i += 1;
}

// while文(break)
var i = 0;
while (true) {
  if (i == 100) {
    break;
  }
  i += 1;
}

// do-while文
var i = 0;
do {
  i += 1;
} while (i < 100);

switch 文

var num = 10;
switch (num) {
  case 1:
    trace("one");
	case 2,3: // カンマ区切りでorの指定も可能
		trace("two,three");
  case _: // 他のケースに該当しない場合
    trace("default");
}

if や switch は式として扱われ、値を返すことができる。

var num = 20;
var max = if (num > 10) {
  10;
} else {
  num;
};

var type = "human";
var strong = switch (type) {
  case "human":
    5;
  case "beast":
    60;
  case _:
    530000;
};

関数

基本的には下記で定義する。

function 関数名(引数:引数の型...) {実装}

optional な引数

// optionalな引数には?を付ける
function great(a:Int, ?b:Int) {
  trace(a);

  // bはNull<Int>型として扱われる
  if (b != null) {
    trace(b);
  }
}

クラス

クラスの定義

class Point {
	// メンバ
	var x:Int;
	var y:Int;

	// メンバ、メソッドのどちらでも、外から使う場合は明示的にpublicをつける
	public var name = "point";

	// コンストラクタは function newで定義。
	public function new(x, y) {
		this.x = x;
		this.y = y;
	}

  // このメソッドはpublicがないので外から使えない
	function trace() {
		trace(this.x, this.y);
	}

	// クラスメソッドはstaticを付ける
	public static function equal(a:Point, b:Point):Bool {
		return a.x == b.x && a.y == b.y;
	}
}

...

var p1 = new Point(1, 2);
trace(Point.equal(p1, new Point(1, 2)), p1.name);

継承

class Point3 extends Point {
	var z:Int;

	public function new(x, y, z) {
		// 継承した場合、newの中で必ずsuperを使う
		super(x, y);
		this.z = z;
	}

	// 親クラスのメソッドを再定義する場合はoverrideを付ける
	override function sum() {
		// super.{メソッド名}で親のメソッドを呼び出せる
		return super.sum() + this.z;
	}
}

インターフェース

interface Printable {
	var type:String;
	function toString():String;
}

class User implements Printable {
	var first:String;
	var last:String;

	// interfaceで定められたメンバはpublicにする必要がある
	public var type:String;

	public function new(first, last) {
		this.first = first;
		this.last = last;
		this.type = "uset";
	}

	// interfaceで定められたこのメソッドがない場合はエラー
	public function toString() {
		return this.first + this.last;
	}
}

インターフェースはクラスと異なり、他のインターフェースを複数継承できる

interface Debuggable extends Printable extends Serializable

アブストラクト

値を使おうとした時に別の型として振舞う。複雑な型変換が必要になる場合などに使われる。

abstract IntPlus5(Int) {
	inline public function new(i:Int) {
		this = i + 5;
	}
}

...

var a = new IntPlus5(5);
trace(a); // 10

@:from

@:from である型からの暗黙な型変換を実装することができる。

abstract IntPlus5(Int) {
	inline public function new(i:Int) {
		this = i + 5;
	}

	
	public static function fromInt(i:Int) {
		return new IntPlus5(i);
	}

	
	public static function fromInt(s:String) {
		return new IntPlus5(Std.parseInt(s));
	}
}

...

var b:IntPlus5 = 4; // 9
var c:IntPlus5 = "90"; // 95

@:to

@:to である型への暗黙な型変換を実装することができる。

abstract IntPlus5(Int) {
	inline public function new(i:Int) {
		this = i + 5;
	}

	...

	
	public function toInt() {
		return this - 5;
	}
}

...

var d:IntPlus5 = 10; // 15
var e:Int = d; // 10

型システム

typedef

別名で型を定義できる

typedef IntArray = Array<Int>;

typedef UserData = {
	var first:String;
	var last:String;
};

typedef Measurable = {
  public var length(default, null):Int;
}

var array:IntArray = [1, 2, 3];
var john:UserData = {first: "John", last: "Smith"};

型パラメータ

他の型を型のパラメータとして含めることができる

class List<T> {
	public function new() {...}

	public function push(x:T) {...};
}

typedef TwinArray<T, U> = {
	var first:Array<T>;
	var second:Array<U>;
};

型パラメータには制約を付けることができる。

interface Human {
	var name:String;
}

interface Wolfman extends Human {
	function beastize():Void;
}

interface Point {
	var x:Int;
	var y:Int;
}

// TはHumanと互換がある必要がある
typedef HumanGroup<T:Human> = Array<T>;

...

var ulfuls:HumanGroup<Wolfman> = []; //OK
var sannenBgumi:HumanGroup<Human> = []; //OK
var points:HumanGroup<Point> = []; //エラー

パターンマッチング

Haxe では、switch 式で複雑なパターンのマッチングを行うことができる。 上から順にマッチングを行い、パターンの考慮漏れがある場合はコンパイルエラーとなる。

enum のマッチング

enum Color {
	Red;
	Green;
	Blue;
	Rgb(r:Int, g:Int, b:Int);
}

switch (Color.Rgb(0, 0, 0)) {
  // | でORの条件にできる
  case Red | Blue | Green:
    trace("primary colors");
  // enumの中の値もマッチングに利用できる
  case Color.Rgb(0, 0, 0):
    trace("Black");
  case Color.Rgb(255, 255, 255):
    trace("White");
  // enumの中の値を使わない場合は_を使う
  case Color.Rgb(_, _, _):
    trace("RGB");
}

配列のマッチング

var trueArray = [1, 2, 3];
var result = switch (trueArray) {
  case []: false;
  case [1, 2]: false;
  case [_, _]: false;
  case [_, 3, 4]: false;
  case [1, 2, 3]: true;
  case _: false;
}

構造体のマッチング

var user:{first:String, middle:String, last:String} = {first: "Luffy", middle: "D", last: "Monkey"};
var isD = switch (user) {
  case {first: "Roger", last: "Gold"}: true;
  case {middle: "D"}: true;
  case _: false;
};

変数のキャプチャ

// _の代わりに変数名を指定してキャプチャできる
switch (Color.Rgb(0, 0, 0)) {
  ...
  case Color.Rgb(r, g, b):
		trace("RGB", r, g, b);
}

キャプチャした変数に対する条件分岐

var myArray = [7, 6];
var s = switch (myArray) {
  // キャプチャした変数がifを満たせば処理に進む。満たさなければ次のマッチングに回す。
  case [a, b] if (b > a):
    b + ">" + a;
  case [a, b]:
    b + "<=" + a;
  case _: "found something else";
}

シングルパターンチェック

enum の値のメソッド。あるパターンにマッチするかどうかを調べることができる。

var green = Color.Rgb(0, 255, 0);
var isRed = green.match(Color.Red | Color.Rgb(255, 0, 0));
trace(isRed);

メタデータ

メタデータはコードの振る舞い自体には影響を与えない。 以下のような用途で用いられる。

  • コンパイルされるバイトコードをチューニングしたい。
  • クラスやそのメンバ、メソッド、関数などに、著者名、作成途中であることのマークなど、付加情報を付けたい。

前者は Compile-time Metadata と呼ばれ、言語にすでに組み込まれているものである。特定の出力ターゲットに対するコンパイル結果の制御を行うことができるため、コンパイル結果に影響を及ぼす。これらはhaxe --help-metasコマンドで一覧を見ることができる。

後者はユーザーが自身で定義するもので、コメントと同様に、コードの振る舞い、コンパイル結果などには影響を与えない。ただし、デバッグ用コマンドでメタデータを確認することができる。

Compile-time Metadata

Compile-time Metadata は、@:{メタデータ名}で定義される。:(コロン)の有無が他のメタデータとの違いで、メタデータに:が入っている場合はコンパイル結果を制御しているものだと考えればいい。下記は一例。

@:generic

型パラメータを含むクラスや型のコンパイル結果を制御する。@:generic がついている場合、ソースコード中で考えられる各型の組み合わせに対し、個別にクラス/型を生成する。これにより、考えられる型の組み合わせごとにバイトコードが作られコンパイル結果のサイズは大きくなってしまうが、型の変換を行い型 に合わせた振る舞いを作るレイヤーが不要になるため、パフォーマンスが改善される。


class MyValue<T> {
  public var value:T;

  public function new(value:T) {
    this.value = value;
  }
}

class Main {
  static public function main() {
    var a = new MyValue<String>("Hello");
    var b = new MyValue<Int>(42);
  }
}
@:from,@:to

前述の@:from,@:to もメタデータ。

@:pure

@:pure は副作用がないことを明示する。コンパイル時の最適化に役立てられる。

カスタムなメタデータ

Meta.get〜で出力できるデータ。動作には全く影響しない。


class GreatClass {
	(1)
	(100)
	var value:Int;

	
	(530000,"desu")
	static function method() {}
}

...

trace(Meta.getType(GreatClass));  // {great: null}
trace(Meta.getStatics(GreatClass)); // {method: {sentouryoku: [530000,desu], todo: null}}
trace(Meta.getFields(GreatClass).value); // {min: [1], max: [100]}

条件付きコンパイル

コンパイルオプションなどによってソースコードのある箇所を評価するかしないか指定することができる。

#if debug
trace("debug");
#else
trace("production!");
#end

haxe --help-definesで利用できるフラグを調べることができる。以下は一例。

debug

-debug オプションがついていると true になる。

interp

—interp オプションがついていると true になる。

macro

マクロを読み込む場合のみ true になる。