Haxe入門 / すぐに実行する

Interpret the program using internal macro sytem

↑の見出しのようにヘルプには書いてあるんだけど、実際コンパイルしてるのかインタプリタ的に動いてるのかは調べてない。 Macro systemもまだ読んでないし。 重要なのはC++Javascriptに変換しないですぐに実行できるという点だ。

Main.hx

class Main {
    static public function main() {
        trace("Hello, World!");
    }
}

実行

$ haxe -main Main --interp

結果

Main.hx:4: Hello, World!

コードのテストには便利ですね。

Haxe入門 / 2.6 Function Type

目次に戻る

2.6 Function type 関数型

(Anonymous structureの続きは後回し。)

関数型(関数の型)はHaxeのユーザはあんまり気にすること無いけど、いろんなとこにホントはあるんだよ(超意訳)。関数型は$typeを使えば確認できて、これ使うと式の型がコンパイル時に出力されます。

class FunctionType {
    static public function main() {
        $type(test); // i : Int -> s : String -> Bool
        $type(test(1, "foo")); // Bool
    }
    
    static function test(i:Int, s:String):Bool {
        return true;
    }
}

出力

Warning : i : Int -> s : String -> Bool

Warning : Bool

$typeの出力は警告扱いのようだ。

test関数の宣言と、$typeの出力は似てるけどちょっと違いますね。$typeの出力の方は

  • 関数の引数i:Ints:Stringはカンマ,じゃなくてアロー->で区切られてます。
  • 関数の返値の型は、もう1つの->の後に表示されます。

どちらの記法でもtest関数が第1引数の型にIntを、第2引数の型にStringをとり、返り値の型はBoolであることはなんとなくわかります。

もし関数呼び出し(test(1,"foo"))が$typeに渡されると、Haxeは引数の型をチェックして、test関数の返り値の型であるBoolを出力します。

関数の引数に関数型を取る場合は()で囲って表示されます。例えば

Int -> (Int -> Void) -> Void

みたいに。この例だと、第1引数の型はInt、第2引数の型は「Int型を引数にとりVoid型を返す関数」の型、そして返り値の型はVoidですね。具体的にはこういうコードです。

class FunctionType {
    static public function main() {
        $type(foo); 
        foo(3,bar);
    }

    static public function foo(x: Int, f: (Int->Void)) {
        f(x);
    }

    static public function bar(y: Int) {
        trace(y);
    }
}

2.6.1 Optional Arguments オプショナル引数

オプショナル引数を使うと関数に渡す引数を省略できる。引数の識別子の先頭に?(question mark)を付けて宣言する。

class OptionalArguments {
  static public function main() {
    $type(test); // ?i : Int -> ?s : String -> String
    trace(test()); // i: null, s: null
    trace(test(1)); // i: 1, s: null
    trace(test(1, "foo")); // i: 1, s: foo
    trace(test("foo")); // i: null, s: foo
  }

  static function test(?i:Int, ?s:String) {
    return "i: " +i + ", s: " +s;
  }
}

test関数は2つのオプショナル引数、つまりInt型の変数iString型の変数sを持つ。これは関数の型から直接とれる(3行目)。上の例だとtest関数は4回よばれてそれぞれ返り値を出力してる。

  1. ひとつ目は引数なしで呼び出し。
  2. 2つ目は1だけを引数に渡して呼び出し。
  3. 3つ目は1"foo"を省略せずに呼び出し。
  4. 4つ目は"foo"だけを引数に渡して呼び出し。

出力結果を見ると、引数が省略された場合はnullとして扱われてる。これはつまり、引数にnull値を与えられるってこと。静的型言語がターゲットの場合、基本型にはnullを入れることができないことに注意。nullを渡せられるのは?をつけているから。この話はNullabilityでも触れている。

最初の3つの例は直感的に多くの人は理解できるはず。でも4つ目に対しては驚く人もいるんじゃないだろうか。 この場合、"foo"Intに代入できないが後ろに適合する型Stringがあるので、オプショナル引数のIntをスキップできる。

2.6.2 Default values デフォルト値

Haxeでは引数に定数のデフォルト値を使うことができる。

class DefaultValues {
  static public function main() {
    // ?i : Int -> ?s : String -> String
    $type(test);
    trace(test()); // i: 12, s: bar
    trace(test(1)); // i: 1, s: bar
    trace(test(1, "foo")); // i: 1, s: foo
    trace(test("foo")); // i: 12, s: foo
  }

  static function test(i = 12, s = "bar") {
    return "i: " +i + ", s: " +s;
  }
}

この例は[オプショナル引数]で出てきたものとよく似てるけど、12"bar"が関数の引数isに 割り当てられるとこだけ違う。呼び出し時に引数が省略された場合nullじゃなくてデフォルト値を使う。

Haxeのデフォルト値は型の一部ではないし、呼び出し側で置換されるわけでもない(関数がインライン化されていなければ。これはよりtypicalなアプローチだ。 いくつかのターゲットでは、コンパイラは引数が省略された場合にまだnullを渡すかもしれない。その場合はこんなコードを関数内に挿入する。

static function test(i = 12, s = "bar") {
    if (i == null) i = 12;
    if (s == null) s = "bar";
    return "i: " +i + ", s: " +s;
}

This should be considered in performance-critical code where a solution without default values may sometimes be more viable. (<= 訳せない)

目次に戻る

Haxe入門 / Anonymous Structure

目次に戻る

2.5 Anonymous Structure 無名構造体

今更だけど訳語は調べずにつけてます。

構造体を使えばデータをひとつのまとまりにできます。 無名構造体とは、明示的に型を定義せずに構造体を作る機能です。

次の例では、xnameというフィールドを持ち、それぞれ12"foo"という値が入ってる無名構造体を作ってます。

class Structure {
    static public function main() {
        var myStructure = { x: 12, name: "foo"};
    }
}

無名構造体を作る場合の文法を説明します。

  • 構造体は{...}で囲まれる。
  • キーと値のペアはコロン:で区切られる。例) x:12
  • この(キー:値)のペアは、カンマ,で区切って複数並べることができる。
  • 値には、Haxeのどんなでも持つことができる。

最後の4つ目のルールから、無名構造体の中に無名構造体を入れ子状に作ることができます。

var user = {
    name : "Nicolas",
    age : 32,
    pos : [{ x : 0, y : 0 },{ x : 1, y : -1 }],
};

無名構造体のフィールドは、クラスの場合と同じようにドット.でアクセスできます。

user.name; // get value of name, which is "Nicolas"
user.age = 33; // set value of age to 33

もちろん無名構造体で定義されてないフィールドにはアクセスできません。あくまでもHaxeの構造体ですから。次の例はコンパイルエラーになります。

class Test {
    static public function main() {
        var point = { x: 0.0, y: 12.0 };
        point.z; // NG!
    }
}

エラーメッセージ

{ y : Float, x : Float } has no field z

このエラーメッセージが出るということは、HaxeコンパイラpointFloat型のフィールドx, yを持つことを知ってるってことです。同時にpointz:Floatというフィールドを持っていないということも。このようにHaxeコンパイラpointの型が何なのか推論します。

場合によっては、pointを他のクラスのフィールドに持ちたいこともあるでしょう。その場合は明示的にpoint無名構造体の型を書くことができます。

class Path {
    var start : { x : Float, y : Float };
    var target : { x : Float, y : Float };
    var current : { x : Float, y : Float };
}

ですがこの書き方は同じ物を何度も書かなければならないので冗長ですし、面倒くさいので普通はtypedefを使って型名を定義します。

typedef Point = { x : Float, y : Float }

class Path {
    var start : Point;
    var target : Point;
    var current : Point;
}

これですっきりしました!

2.5.1 JSON for Structure Values 構造体の値としてJSON記法を使う。

文字列リテラルをキーに使えばJavaScript Object Notaton(JSON)でも構造体を作れます。

var point = { "x" : 1, "y" : -5 };

どんな文字列リテラルでも使えますが、Haxe識別子として有効なものだけがフィールドとしてみなされます。もし識別子として正しくなければ、フィールドにアクセスできず、Reflect.fieldReflect.setFieldといったリフレクションを使わなくてはならなくなります。上の例だと、x,yはともに識別子として有効なので、

trace(point.x);

とアクセスできます。しかし、

var invalid_field = { "$": 3, "y":4 };
trace(invalid_field.$); // エラー

これはできません。$Haxeでは有効な識別子ではないからです。識別子についての記載はにあります。

2.5.2 Class Notation for Structure Types 構造体の型にクラス記法を使う

クラスの型を定義する際に、クラスフィールドと同じ文法を使えます。次の例ではtypedefを使ってxyというInt型のフィールド変数を持つPointという型を定義してます。

typedef Point = {
    var x : Int;
    var y : Int;
}

2.5.4 Impact on Performance パフォーマンスへの影響

(2.5.3はどこ行った?)

構造体や、構造的部分型動的型言語へのコンパイルではパフォーマンスへの影響はありません。しかし静的型言語に対しては実行時名前解決(dynamic lookup)が行われるため、大抵は静的フィールドへのアクセスよりも遅くなります。

目次に戻る

Haxe入門 / Enum Instance

目次に戻る

2.4 Enum Instance 列挙インスタンス

Haxeはデータ構造を表現するための強力な列挙型がある。いわゆる代数的データ型(ADT: algebraic data type)。でもこんな難しい言葉は無視しても良いと思います。

列挙型はを持てないことに注意。

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

この列挙型Colorは、Red, Green, Blue, あるいはRGBで指定した任意の色、のいずれかを表す。順にコードを説明すると、

  • enumキーワードで列挙型を宣言。
  • Colorは列挙型の名前。型指定子として使える名前であればなんでも取れる。なぜかリンク先に何も書いてないが、型指定子についてはの方にそれっぽい記述がある。
  • {}で囲まれた中身を、enum constructors (列挙コンストラクタ)という。
  • Red, Green, Blueは引数なし。
  • Rgbは3つのInt型引数r, g, bを取る。

Colorが列挙型、{...}の中身が列挙コンストラクタ

Haxeの型システムには、すべての列挙型を統一的に扱う型Enum<T>がある。 Enum<T>コンパイル時にすべての列挙型に共通な基底型とされる。 例えば、Enum<Color>という型のインスタンスColorという型。

var x: Enum<Color> = Color; // OK
trace(Type.allEnums(x)); // [Red,Green,Blue]

他の言語にもあったりするけど、型のインスタンスが型というのは馴染みがないかも。

ただし、Haxeコンパイラによって生成されたコードにはEnum<T>と個別の列挙型との関係は反映されない。(という解釈であってる?)

2.4.1 Enum Constructor 列挙コンストラクタ

クラスの場合と同じように、列挙コンストラクタを使うと列挙型のインスタンスを作れる。ただしクラスとは違って列挙型には複数のコンストラクタ(Red, Green, Blue, Rgb)があるので、そのうちのどれかを使ってインスタンス化する。

var a = Red;
var b = Green;
var c = Rgb(255, 255, 0);

この例では変数a, b, cの型はColor。変数cRgbコンストラクタを使って初期化されている。

すべての列挙インスタンスの型はEnumValueという特殊な型でもある。Haxe標準ライブラリはこのEnumValueを使って列挙インスタンスに対する統一的な演算方法を定義してる。

列挙型と列挙コンストラクタには重要な違いがある。

import Color;

class EnumUnification {
    static public function main() {
        var ec:EnumValue = Red; // valid
        var en:Enum<Color> = Color; // valid
        //var x:Enum<Color> = Red; // ※ Compile Error: Color should be Enum<Color>
    }
}

このコードの7行目(※)をコメントアウトするとコンパイルは通らない。 何故かというと、Redは列挙コンストラクタで、Enum<Color>は列挙型だから。列挙コンストラクタを列挙型に代入することはできない。クラスでいう、クラスのインスタンスをクラスの型に代入できないのと同じ。

目次に戻る

Haxe入門 / Class Instance

目次に戻る

2.3 Class Instance

2.3.1 Class constructor コンストラクタ

クラスのインスタンスは、コンストラクタを呼び出すことで作れる(インスタンス化)。インスタンスのことをオブジェクトと呼ぶこともあるけどHaxeでは、enum instanceとの対応も考えてインスタンスと呼びたいらしい。まあどうでもいい。

実際のインスタンス化の例

var p = new Point(-1, 65);

これはPointクラスのインスタンスを作っている。Pointクラスのコンストラクタは2つの引数(-1と65)をとる。Pointクラスの場合はこれらの変数はそれぞれ、xyというインスタンス変数に割り当てられる。

簡単にまとめると、 newを使えばコンストラクタを呼び出せて、インスタンスが作れる。

newは5.11 newで詳しく説明する。

2.3.2 Inheritance 継承

クラスは他のクラスからextendsキーワードを使って継承できる。

class Point3 extends Point {
    var z : Int;
    public function new(x,y,z) {
        super(x,y);
        this.z = z;
    }
}

この場合、Point3の親クラスはPoint、言い換えればPointクラスの子クラスはPoint3。

継承とは、親クラスの機能を拡張するための方法である。基本的な機能は親クラスと同じであるため、拡張部分だけを記述する。親となるクラスを指定するためにextendsキーワードを使う。それだけである。上の例で言えば、Point3はx,y,zの情報を持つクラスである。x,yの情報を持つクラスはPointがあるので、これにzを拡張したということ。

さて、上のクラスではコンストラクタを定義している。

public function new(x,y,z) { ... }

これはコンストラクタで呼び出しで実行される。例) var p = new Point3(1,2,3)

コンストラクタ中のsuper(x,y)は親クラスのコンストラクタを呼び出している。 クラスを継承しているからといって、必ずしも子クラスで新しくコンストラクタを定義する必要はない。ただし定義する場合は必ず親クラスのコンストラクタを呼びださなければならない。呼び出すのはコンストラクタの最初でも途中でも最後でも構わない。

子クラスは親クラスの関数をオーバーライドできる。明示的にoverrideキーワードを使って指定する。

class Child extends Parent {
    public override function someMethod() { ... }
}

オーバーライドすると、親クラスの関数を別の挙動をするように上書きできる。

詳しくは4.3.1 Overriding Methodsで説明する。

2.3.3 Interfaces インターフェイス

インターフェイスの役割はクラスが持つ変数や関数の名前を示すこと。名前を出すのが目的なので実装(implements)は書かない。実装ってのはつまり関数の中身 { ... }のこと。

interface Printable {
     public function toString(): String; // 関数の中身は書かない。
}

クラスと似てるけど、以下の点でクラスとは違う。

  • classキーワードじゃなくてinterfaceキーワードを使う。
  • 関数は実装(正確には)を持たない。
  • すべてのフィールド(変数、プロパティ、関数)は型を明示しなきゃならない。

インターフェイス構造的部分型と違って、クラス間の静的関係を示す。構造的部分型はまだ読んでないので知らない。ちなみに性的関係ではない。

静的関係(static relation)はOfficialサイトには強調して書いてあるんですが、そんな単語書かれたってわけわからん。これ書いたやつ頭悪いんちゃうか。あなたの書いた静的関係と私の考える性的関係は違うかもしれんのですよ。まあ親切に推測すると、インターフェイスに書かれた名前とクラスに書かれた名前は型とかも含めて見たまんま一致しなければならないっちゅーことですかね?

気を取り直して、インターフェイスの使い方。

class Point implements Printable { }

implementsってキーワードを使って、PointクラスはPrintableインターフェイスの関数や変数を実装することを示します。継承と似ていて、PointクラスのインスタンスはPrintableインターフェイスインスタンスにもなります。継承と違うのは、クラスは複数のインターフェイスを親にとれるという点です。

class Point implements Printable implements Serializable { }

implementsキーワードがあると、Haxeコンパイラインターフェイスに書かれた関数名や変数名が、実際にクラスで実装されているかチェックします。この時チェックするクラスは、その親クラスも含まれます。 つまり、

class Point extends Base implements Printable { }

とした場合は、PointクラスがPrintableの関数名を実装していなくともBaseクラスで実装してあればOKです。

上でもちらっと書きましたがインターフェイスで書けるフィールドは関数だけではなく、変数やプロパティも含まれます。

interface Placeable {
     public var x:Float;
     public var y:Float;
}
class Main implements Placeable {
     public var x:Float;
     public var y:Float;
     static public function main() { ... }
}

もちろん、インターフェイスで変数名を書いたら実装先のクラスでも同一名の変数を書かなければなりません。 ちなみに、Haxe2までは複数のインターフェイスをimplementsする際、カンマで区切って並べる必要がありましたけど、Haxe3では必要無くなりました。数少ないHaxe2とHaxe3の非互換性だそうです。

目次に戻る

Haxe入門 / Nullability

目次に戻る

2.2 Nullability

Definition of nullable

型がnullableであるとは、その型のインスタンスnullを持てるということ(意訳)

nullableかどうかはターゲットとする言語によって違う

基本型がnullableかどうか(nullを持てるか)はターゲットとする言語によって変わる。例えば、C++,Java,C#などの静的型システムを持つ言語(静的型言語)だと基本型はnullableじゃない(nullをとれない)。対して、JavaScriptPHPなどの動的型システムを持つ言語(動的型言語)は基本型もnullable(nullを持てる)。だから動的型言語だとあまり気にしなくていいけど、静的型言語だと注意しよう。静的型言語だと基本型はnullableじゃない。

実際、

var a:Int = null;

とすると、動的型言語だとコンパイルが通る。静的型言語だと

On static platforms, null can't be used as basic type Int

というエラーが出る。

とは言っても、動的型言語か静的型言語か意識しなければならないというのはHaxeを使う意味が薄れるというもの。Intにnullを持たせたいこともあるだろう。そういう場合はNull<T>を使う。

var b: Null<Int> = null;
var c: Null<Int> = 1;

これは動的型言語でも静的型言語でも通る。

比較演算でも

var a:Int = 0;
if(a==null) { ... }

はエラーになるが、

var a:Null<Int> = 0;
if(a==null) { ... }

はエラーにならない。

Nullは基本型にも代入できる。

var a:Null<Int> = null;
var b:Int = a;

ただしこの挙動はターゲットとする言語によって異なる。静的型言語だとb==0となるが、動的型言語だとb==nullとなる。

2.2.1 Optional Arguments and Nullability Optional引数とNullability

詳しくは2.6.1 Optional Argumentsで説明。ここでは簡単に。

Haxeでは関数呼び出しで引数を省略する方法が2つある。

  • Optional Arguments (オプショナル引数)
  • Defalut values (デフォルト引数)

オプショナル引数を使った関数定義では、引数指定を省略するとnullが渡される。一方、デフォルト引数では関数定義時に引数が省略された場合の値を指定できる。こう書くと、デフォルト引数の方が汎用的で良いと思うかもしれないが、オプショナル引数は型システムを利用した引数の省略方法で、デフォルト引数はそうではないとか。だからデフォルト引数は、パフォーマンス的に悪いこともあるらしい(私が誤解してる可能性あるので信じないこと)。

オプショナル引数はnullになるかもしれないので、当然nullableである。nullableがわかんない人はこの記事の最初に戻ること。

オプショナル引数の使い方は関数定義時に、変数の前に?(question-mark)を置く。

function foo(?x: Int) { ... }

xはnullableではなくてはいけないので、xの型はNull<Int>になる。静的型言語ではIntはnullableではないことに注意。もちろん

function foo(?x: Null<Int>) { ... }

と書いても同じ。でもわざわざNull<Int>と書かなくてもオプショナル引数だから勝手にそうなる。

目次に戻る

Haxe入門 / Basic Types

言語リファレンスを読んでいく。まとめない方が読み進めるの速いけどブログネタがなくて継続が途切れるという事情。

目次に戻る

2.1 Basic Types 基本型

基本型は

  • Bool (論理型)
  • Float (浮動小数点型)
  • Int (整数型)

Basic Typesはabstract typesであるとか書いてある。abstract typesは読んでないので知らないけど、コンパイル時にプラットフォーム固有の型に置き換えられる?らしい。

2.1.1 Numeric types 数値型

FloatとIntの2つ。Floatは(おそらくIEEE754の)64bitに準拠。IntはFloatに変換できる。でもFloatからIntへの変換は丸め誤差とかあるし、暗黙的変換はできない。試してみる

var a:Float = 3.0;
var b:Int;
b = a;

Float shoukd be Int

というエラーがでた。なるほど。

2.1.2 Overflow

パフォーマンス落ちるので、オーバーフローのチェックはしない。挙動はターゲットの言語によって異なる。例えばC++,Java,C#とかだと普通の32bit符号付き整数な扱いだし、PHPとかJavascriptだとInt型なんてなくてfloatが上限となる(252)

32bit整数、64bit整数を明示的に使いたい場合はhaxe.Int32, haxe.Int64がある。これを利用した場合のコストは言語依存。

2.1.3 Numeric Operators 数値型の演算

算術演算

数値型に対してよくある演算は定義されてる。

++, --, +, -, *, /, %

インクリメント(++)/デクリメント(--)はInt、Float両方に使える。

四則演算(+.-,*,/)、剰余(%)も定義されてる。剰余にもFloat使えるらしい。へー。

IntとFloatとの演算は、Floatで返されるらしい。多くの他言語と同じですね。

比較演算

比較演算子もよくある定義。

==, !=, <, <=, >, >=

が使えます。返り値はBool型。

ビット演算

ビット演算はIntに対してのみ

~, &, |, ^, <<, >>, >>>

順に、ビット反転, and, or, xor, 左シフト、右シフト、論理右シフト

2.1.4 Bool 論理型

論理型がとる値は2つ。trueかfalse

演算

&&, ||, !

でand, or, notが使える。

A && B

に対してはAが先に評価され、Aがtrueの場合のみBも評価される。Aがfalseの場合、Bは評価されない(つまりBに到達しない)。なぜこういう挙動かというと、AがfalseならばBを評価するまでもなく、A&&BはfalseなのでBを評価する意味が無い。

A || B

に対しても同じ。AがtrueならばBを評価しても無駄なので、Bは評価されない。

このような挙動であるため、

if(object != null && object.field == 1) { ... }

という書き方ができる。objectがnullのとき、object.fieldにアクセスしようとするとランタイムエラーとなる。でもobject!=nullでチェックしておけば、object.fieldへのアクセスは起こらないのでランタイムエラーは起きない。

2.1.5 Void

Voidは特殊な型で、実際には型ではない。型というよりも、型がないことを示したいときに使う。主に関数の引数や返り値で使われる。

class HelloWorld {
    static public function main():Void {
        trace("Hello World");
    }
}

このmain関数はまさにその例で、形式的にはVoid型の引数をとってVoid型の返り値を返す。実際にはVoidなので、引数をとらないし、返り値もない。Void型の変数というものはない。

var x:Void;

とVoid型の変数を定義しようとすると

Arguments and variables of type Void are not allowed

というコンパイルエラーになる。

以上Basic typesでした。

目次に戻る