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:Int
とs: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
型の変数i
とString
型の変数s
を持つ。これは関数の型から直接とれる(3行目)。上の例だとtest
関数は4回よばれてそれぞれ返り値を出力してる。
- ひとつ目は引数なしで呼び出し。
- 2つ目は
1
だけを引数に渡して呼び出し。 - 3つ目は
1
と"foo"
を省略せずに呼び出し。 - 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"
が関数の引数i
とs
に
割り当てられるとこだけ違う。呼び出し時に引数が省略された場合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 無名構造体
今更だけど訳語は調べずにつけてます。
構造体を使えばデータをひとつのまとまりにできます。 無名構造体とは、明示的に型を定義せずに構造体を作る機能です。
次の例では、x
とname
というフィールドを持ち、それぞれ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コンパイラはpoint
がFloat
型のフィールドx, y
を持つことを知ってるってことです。同時にpoint
がz: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.field
とReflect.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
を使ってx
とy
という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
。変数c
はRgb
コンストラクタを使って初期化されている。
すべての列挙インスタンスの型は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クラスの場合はこれらの変数はそれぞれ、x
とy
というインスタンス変数に割り当てられる。
簡単にまとめると、 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; // 関数の中身は書かない。 }
クラスと似てるけど、以下の点でクラスとは違う。
インターフェイスは構造的部分型と違って、クラス間の静的関係を示す。構造的部分型はまだ読んでないので知らない。ちなみに性的関係ではない。
静的関係(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をとれない)。対して、JavaScript、PHPなどの動的型システムを持つ言語(動的型言語)は基本型も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でした。