haXe2
haXe 2 Language Reference (Last Updated: 24 Sep 2010)
1
haXe2
Table of Contents 1. Basic Types!
6
2. Syntax!
7
2.1. Constants!
7
2.2. Operations!
7
2.3. Unary operations!
8
2.4. Parentheses!
8
2.5. Blocks!
8
2.6. Local Variables!
9
2.8. Field access!
10
2.9. Calls!
10
2.10. New!
10
2.11. Arrays!
10
2.12. If!
11
2.13. While!
11
2.14. For!
12
2.15. Return!
12
2.16. Break and Continue!
13
2.17. Exceptions!
13
2.18. Switch!
14
2.19. Local Functions!
14
2.20. Anonymous Objects!
15
3. Type Inference!
16
3.1. Printing a Type!
16
3.2. Local variable inference!
16 2
haXe2 3.3. Function types inference!
16
3.4. Inference and type parameters!
17
3.5. User Choice!
18
4. Object Oriented Programming!
19
4.1. Classes!
19
4.2. Constructor!
20
4.3. Class Inheritance!
20
4.4. Extends!
20
4.5. Implements!
21
4.6. Interfaces!
21
4.7. Helper Classes!
21
5. Type Parameters!
23
5.1. Constraint Parameters!
23
6. Enums!
24
6.1. Constructors parameters!
24
6.2. Switch on Enum!
25
6.3. Switch with Constructor Parameters!
25
6.4. Enum Type Parameters!
26
6.5. Using Enums as default value for parameters!
26
7. Packages and Imports!
27
7.1. Imports!
27
7.1.1. Applying additional static methods to class instances!
28
7.1.2. Importing a single type from a module!
28
7.1.3. Importing using wildcards ('*')!
29
7.2. Type Lookup!
29
7.3. Enum Constructors!
29
8. Dynamic!
30
8.1. Parameterized Dynamic Variables! 3
31
haXe2 8.2. Implementing Dynamic!
31
8.3. Type Casting!
32
8.4. Untyped!
32
8.5. Unsafe Cast!
32
9. Advanced Types!
33
9.1. Anonymous!
33
9.2. Typedef!
33
9.2.1. As aliases!
34
9.2.2. Private members!
34
9.2.3. Private types!
35
9.2.4. Accessing Typedefs From Other Packages!
35
9.3. Functions!
35
9.4. Unknown!
36
9.5. Extensions!
36
10. Iterators!
38
10.1. Implementing Iterator!
38
10.2. Iterable Objects!
39
11. Properties!
40
11.1. Sample!
40
11.2. Important Remarks!
41
11.3. Dynamic Property!
41
11.4. Null Property!
42
11. 5. Never Property!
42
12. Optional Arguments!
44
12.1. Default values!
44
12.2. Ordering!
44
12.3. Enum Values!
45
12.4. Warning!
45 4
haXe2 13. Conditional Compilation!
46
14. Inline!
47
14.1. Inlining Static Variables!
47
14.2. Inlining Methods!
47
15. Keywords!
49
5
haXe2
1. Basic Types The haXe syntax is Java/ActionScript/C++ like. A source code file is composed of an optional package name followed by several imports and type declarations. By convention, package names are composed of several identifiers each of which start with a lowercase letter and are separated from one another by periods ".", while type identifiers always start with an uppercase letter. There are several kinds of types. The two important ones are classes and enums. Here are some of the basic types as declared in the standard library : enum Void { } class Float { } class Int extends Float { } enum Bool { true; false; } enum Dynamic<T> { }
Let's see each type one by one : 1
Void is declared as an enum. An enumeration lists a number of valid constructors. An empty enumeration such as Void does not have any constructor. However, it's still a valid type that can be used.
2
Float is a floating point number class. It doesn't have any method so it can be greatly optimized on some platforms.
3
Int is an integer. It doesn't have methods either but it inherits from Float, so it means that everywhere a Float is requested, you can use an Int, while the contrary is not true. And that seems pretty correct.
4
Bool is an enumeration, like Void, but it has two instances true and false. As you can see, even standard types can be defined easily using the haXe type system. It also means you can use it to define your own types.
5
Dynamic is an enum with a type parameter. We will explain how to use type parameters later in this document.
6
haXe2
2. Syntax In haXe, all expressions have the same level. It means that you can nest them together recursively without any problem. For example : foo(if (x == 3) 5 else 8). As this example shows, it also means that every expression returns a value of a given type. 2.1. Constants The following constant values can be used : 0; // Int -134; // Int 0xFF00; // Int 123.0; // Float .14179; // Float 13e50; // Float -1e-99; // Float "hello"; // String "hello \"world\" !"; // String 'hello "world" !'; // String true; // Bool false; // Bool null; // Unknown<0> ~/[a-z]+/i; // EReg : regular expression
You will notice that null has a special value that can be used for any type, and has different behavior than Dynamic. It will be explained in detail when introducing type inference. 2.2. Operations The following operations can be used, in order of priority : 1
v = e : assign a value to an expression, returns e
2
+= -= *= /= %= &= |= ^= <<= >>= >>>= : assign after performing the corresponding operation
3
e1 || e2 : If e1 is true then true else evaluate e2 . Both e1 and e2 must be Bool.
4
e1 && e2 : If e1 is false then false else evaluate e2 . Both e1 and e2 must be Bool.
5
e1...e2 : Build an integer iterator (see later about Iterators).
6
== != > < >= <= : perform normal or physical comparisons between two expressions sharing a common type. Returns Bool.
7
| & ^ : perform bitwise operations between two Int expressions. Returns Int. 7
haXe2 8
<< >> >>> : perform bitwise shifts between two Int expressions. Returns Int.
9
e1 + e2 : perform addition. If both expressions are Int then return Int else if both expressions are either Int or Float then return Float else return String.
10 e1 - e2 : perform subtraction between two Int or Float expressions. Return Int if both are Int and return Float if they are either Float and Int. 11 e1 * e2 : multiply two numbers, same return type as subtract. 12 e1 / e2 : divide two numbers, return Float. 13 e1 % e2 : modulo of two numbers, same return type as subtract. 2.3. Unary operations The following unary operations are available : 1
! : boolean not. Inverse the expression Bool value.
2
- : negative number, change the sign of the Int or Float value.
3
++ and -- can be used before or after an expression. When used before, they first increment the corresponding variable and then return the incremented value. When used after, they increment the variable but return the value it had before incrementation. Can only be used with Int or Float values.
4
~ : ones-complement of an Int.
Note: ~ is usually used for 32-bit integers, so it will not provide expected results with Neko 31-bits integers, that is why it does not work on Neko. 2.4. Parentheses Expressions can be delimited with parentheses in order to give a specific priority when performing operations. The type of ( e ) is the same as e and they both evaluate to the same value. 2.5. Blocks Blocks can execute several expressions. The syntax of a block is the following : { e1; e2; // ... eX; }
A block evaluates to the type and value of the last expression of the block. For example : { f(); x = 124; true; }
This block is of type Bool and will evaluate to true. 8
haXe2 As an exception, the empty block { } evaluates to Void. 2.6. Local Variables Local variables can be declared in blocks using var, as the following samples show: { var var var var var
x; y = 3; z : String; w : String = ""; a, b : Bool, c : Int = 0;
}
A variable can be declared with an optional type and an optional initial value. If no value is given then the variable is null by default. If no type is given, then the variable type is Unknown but will still be strictly typed. This will be explained in details when introducing type inference. Several local variables can be declared in the same var expression. Local variables are only defined until the block they're declared in is closed. They can not be accessed outside the block in which they are declared. 2.7. Identifiers When a variable identifier is found, it is resolved using the following order : 1
local variables, last declared having priority
2
class members (current class and inherited fields)
3
current class static fields
4
enum constructors that have been either declared in this file or imported enum Axis { x; y; z; } class C { static var x : Int; var x : Int; function new() { { // x at this point means member variable this.x var x : String; // x at this point means the local variable } } function f(x : String) { 9
haXe2 // x at this point means the function parameter } static function f() { // x at this point means the class static variable } } class D { function new() { // x means the x Axis } }
Type identifiers are resolved according to the imported packages, as we will explain later. 2.8. Field access Object access is done using the traditional dot-notation : o.field
2.9. Calls You can call functions using parentheses and commas in order to delimit arguments. You can call methods by using dot access on objects : f(1,2,3); object.method(1,2,3);
2.10. New The new keyword is used in expressions to create a class instance. It needs a class name and can take parameters : a = new Array(); s = new String("hello");
2.11. Arrays You can create arrays directly from a list of values by using the following syntax : var a : Array<Int> = [1,2,3,4];
Please notice that the type Array takes one type parameter that is the type of items stored into the Array. This way all operations on arrays are safe. As a consequence, all items in a given Array must be of the same type.
10
haXe2 You can read and write into an Array by using the following traditional bracket access : first = a[0]; a[1] = value;
The array index must be of type Int. 2.12. If Here are some examples of if expressions : if (life == 0) destroy(); if (flag) 1 else 2;
Here's the generic syntax of if expressions : if( expr-cond ) expr-1 [else expr-2]
First expr-cond is evaluated. It must be of type Bool. Then if true then expr-1 is evaluated, otherwise, if there is an expr-2 then it is evaluated instead. If there is no else, and the if expression is false, then the entire expression has type Void. If there is an else, then expr-1 and expr-2 must be of the same type and this will be the type of the if expression : var x : Void = if( flag ) destroy(); var y : Int = if( flag ) 1 else 2;
In haXe, if is similar to the ternary C a?b:c syntax. As an exception, if an if block is not supposed to return any value (like in the middle of a Block) then both expr-1 and expr-2 can have different types and the if block type will be Void. 2.13. While While are standard loops that use a precondition or postcondition : while( expr-cond ) expr-loop; do expr-loop while( expr-cond );
For example : var i = 0; while( i < 10 ) { // ... i++; }
11
haXe2 Or using do...while : var i = 0; do { // ... i++; } while( i < 10 );
Like with if, the expr-cond in a while-loop type must be of type Bool. Another useful example will produce a loop to count from 10 to 1: var i = 10; while( i > 0 ) { ....... i--; }
2.14. For For loops are little different from traditional C for loops. They're actually used for iterators, which will be introduced later in this document. Here's an example of a for loop : for( i in 0...a.length ) { foo(a[i]); }
2.15. Return In order to exit from a function before the end or to return a value from a function, you can use the return expression : function odd( x : Int ) : Bool { if( x % 2 != 0 ) return true; return false; }
The return expression can be used without argument if the function does not require a value to be returned : function foo() : Void { // ... if( abort ) return; // .... }
12
haXe2 2.16. Break and Continue These two keywords are useful to exit early a for or while loop or to go to the next iteration of a loop : var i = 0; while( i < 10 ) { if( i == 7 ) continue; // skip this iteration. // do not execute any more statements in this block, // BUT go back to evaluating the "while" condition. if( flag ) break; // stop early. // Jump out of the "while" loop, and continue // execution with the statement following the while loop. }
2.17. Exceptions Exceptions are a way to do non-local jumps. You can throw an exception and catch it from any calling function on the stack : function foo() { // ... throw new Error("invalid foo"); } // ... try { foo(); } catch( e : Error ) { // handle exception }
There can be several catch blocks after a try, in order to catch different types of exceptions. They're tested in the order they're declared. Catching Dynamic will catch all exceptions : try { foo(); } catch( e : String ) { // handle this kind of error } catch( e : Error ) { // handle another kind of error } catch( e : Dynamic ) { // handle all other errors }
All the try and the catch expressions must have the same return type except when no value is needed (same as if).
13
haXe2 2.18. Switch Switches are a way to express multiple if...else if... else if test on the same value: if( v == 0 ) e1 else if( v == foo(1) ) e2 else if( v == 65 || v == 90 ) e3 else e4;
Will translate to the following switch : switch( v ) { case 0: e1; case foo(1): e2; case 65, 90: e3; default: e4; }
Note: In the example above, a case statement reads "65, 90". This is an example where a case expects to match either of the two (or several) values, listed as delimited by comma(s). Switches in haXe are different from traditional switches : all cases are separate expressions so after one case expression is executed the switch block is automatically exited. As a consequence, break can't be used in a switch and the position of the default case is not important. On some platforms, switches on constant values (especially constant integers) might be optimized for better speed. Switches can also be used on enums with a different semantic. It will be explained later in this document. 2.19. Local Functions Local functions are declared using the function keyword but they can't have a name. They're values just like literal integers or strings : var f = function() { /* ... */ }; f(); // call the function
14
haXe2 Local functions can access their parameters, the current class statics and also the local variables that were declared before it : var x = 10; var add = function(n) { x += n; }; add(2); add(3); // now x is 15
However, local functions declared in methods cannot access the this value. You then need to declare a local variable such as me : class C { var x : Int; function f() { // WILL NOT COMPILE var add = function(n) { this.x += n; }; } function f2() { // will compile var me = this; var add = function(n) { me.x += n; }; } }
2.20. Anonymous Objects Anonymous objects can be declared using the following syntax : var o = { age : 26, name : "Tom" };
Please note that because of type inference, anonymous objects are also strictly typed.
15
haXe2
3. Type Inference Type Inference means that the type information is not only checked in the program, it's also carried when typing, so it doesn't have to be resolved immediately. For example a local variable can be declared without any type (it will have the type Unknown) and when first used, its type will be set to the corresponding one. 3.1. Printing a Type Anywhere in your program, you can use the type operation to know the type of a given expression. At compilation, the type operation will be removed and only the expression will remain : var x : Int = type(0);
This will print Int at compilation, and compile the same program as if type was not used. This is useful to quickly get a type instead of looking at the class or some documentation. 3.2. Local variable inference Type Inference enables the whole program to be strictly typed without any need to put types everywhere. In particular, local variables do not need to be typed, their types will be inferred when they are first accessed for reading or writing : var loc; type(loc); // print Unknown<0> loc = "hello"; type(loc); // print String
3.3. Function types inference Declaring the type of parameter passed to a class method or local function is also optional. The first time the function is used, the type of the parameter will be set to the type it was used with, just like local variables. This can be tricky since it will depend on the order in which the program is executed. Here's an example that shows the problem : function f( posx ) { // .... } // ... f(134); f(12.2); // Error : Float should be Int
The first call to f sets the type of posx to Int. The second call to f causes a compilation error because f is now expecting an Int, not a Float. However if we reverse the two calls to f, the value type is set to Float first. A second call using an Int does not fail since Int is a subtype of Float. 16
haXe2 function f( posx ) { // .... } // ... f(12.2); // Sets the parameter type to Float f(134); // Success
In this example the two calls are near each other so it's quite easy to understand and fix. In larger programs with more complex cases, fixing such compilation problems can be tricky. The easiest solution is to explicitly set the type of the function. Then the call that was responsible for the problem will be displayed when recompiling. Drawing from the first example: function f( posx : Int ) { // .... } // ... f(134); f(12.2); // Failure will point to this line
3.4. Inference and type parameters function f<T>(p: Class<T>): T { } function g(p: Float) { } var a: Dynamic = {}; var b = f(a); type(b); //At this point the type of 'b' is not inferred and is traced as 'Unknown<0>' g(b); type(b); //Type of 'b' is from now on known to be 'Float'
The code above illustrates a situation with a function call that initializes a variable declared without type - the type inferring does not occur at that point, because the combination of the function being called and call parameters makes it impossible. The 'type parametrized' function f depends on its first parameter of type Class<T> (T being an abstract, see "Type parameters" doc. page), to decide what type it returns. When passed a Dynamic object however, it is clear that the type parameter 'T' makes no sense and is unknown. Type inferring is delayed until further, when a call to g that takes a Float is able to tell the compiler to specify type of b as Float. Hence the first type 17
haXe2 statement traces "Unknown<0>" and the second - "Float". After the type is inferred, it is naturally immutable. 3.5. User Choice Using type inference is a choice. You can simply not type your variables and functions and let the compiler infer the types for you, or you can type all of them in order to have more control on the process. The best is maybe in the middle, by adding some typing in order to improve code documentation and still be able to write quickly some functions without typing everything. In all cases, and unless you use dynamics (they will be introduced later), your program will be strictly typed and any wrong usage will be detected instantly at compilation.
18
haXe2
4. Object Oriented Programming 4.1. Classes We will quickly introduce the structure of classes that you might already be familiar with if you've done some OO programming before : package my.pack; /* this will define the class my.pack.MyClass */ class MyClass { // .... }
A Class can have several variables and methods. package my.pack; class MyClass { var id : Int; static var name : String = "MyString"; function foo() : Void { } static function bar( s : String, v : Bool ) : Void { } }
Variables and methods can have the following flags : 1
static : the field belongs to the Class itself and not to instances of this class. Static identifiers can be used directly in the class itself. Outside of the class, it must be used with the class name (for example : my.pack.MyClass.name).
2
dynamic: the field can be dynamically rebound.
3
override: the field is being overridden in a subclass.
4
public : the field can be accessed from outside of the class.
5
private : the field access is restricted to the class itself and to classes that subclass or extends it. This ensures that the class internal state is not accessible. By default, all fields are private. This corresponds to the keyword protected in most other languages such as Java, PHP
All class variables must be declared with a type (you can use Dynamic if you don't know which type to use). Function arguments and return types are optional but are still strictly checked as we will see when introducing type inference.
19
haXe2 Non-static variables may not have an initial value. Static variables can have an initial value, although it is not required. 4.2. Constructor The class can have only one constructor, which is the not-static function called new. This is a keyword that can also be used to name a class function : class Point { public var x : Int; public var y : Int; public function new() { this.x = 0; this.y = 0; } }
Constructor parametrization & overloading : public function new( x : Int, ?y : Int ) { this.x = x; this.y = (y == null) ? 0 : y; // "y" is optional }
4.3. Class Inheritance When declared, it's possible that a class extends one class and implements several classes or interfaces. This means the class will inherit several types at the same time, and can be treated as such. For example : class D extends A, implements B, implements C { }
Every instance of D will have the type D but you will also be able to use it where an instance of type A, B or C is required. This means that every instance of D also has the types A , B and C. 4.4. Extends When extending a class, your class inherits from all public and private non-static fields. You can then use them in your class as if they where declared here. You can also override a method by redefining it with the same number of arguments and types as its superclass. Your class can not inherit static fields.
20
haXe2 When a method is overridden, then you can still access the superclass method using super : class B extends A { override function foo() : Int { return super.foo() + 1; } }
In your class constructor you can call the superclass constructor using also super : class B extends A { function new() { super(36,""); } }
4.5. Implements When implementing a class or an interface, your class is required to implement all the fields declared or inherited by the class it implements, with the same type and name. However the field might already be inherited from a superclass. 4.6. Interfaces An Interface is an abstract type. It is declared using the interface keyword. By default, all interface fields are public. Interfaces cannot be instantiated. interface PointProto { var x : Int; var y : Int; function length() : Int; }
An interface can also implement one or several interfaces : interface PointMore implements PointProto { function distanceTo( p : PointProto ) : Float; }
4.7. Helper Classes In haXe, it is possible to have more than one class definition per class file: // both definitions in Foo.hx class Foo { ... } class FooHelper { ... }
This is fairly common in many object oriented languages such as Java. However, unlike other such languages, haXe also allows for these internal helper classes to be publicly available outside of the main class. By importing the main class, the helper class becomes available: 21
haXe2 // in Bar.hx import Foo; class Bar{ var b:FooHelper; }
The helper class can also be made accessible via an extended package declaration: // in Bar.hx class Bar{ var b:Foo.FooHelper; }
The use of public internal helper classes is typically not a recommended practice in most languages. Since helper class names do not correspond with their .hx file names, they can be harder to find in source trees. HaXe lets you mark an internal helper class with the private keyword, in the same way that fields are marked: // both definitions in Foo.hx class Foo { ... } private class FooHelper { ... }
This prevents the internal helper class from being accessed outside of the main class. Unless there is a good reason for their use, consider placing each public class definition in its own .hx file, or make the internal class private, to prevent its definitions being included (and possibly causing a name conflict) when the main class is imported.
22
haXe2
5. Type Parameters A class can have several type parameters that can be used to get extensible behavior. For example, the Array class has one type parameter: class Array<T> { function new() { // ... } function get( pos : Int ) : T { // ... } function set( pos : Int, val : T ) : Void { // ... } }
Inside the Array class, the type T is abstract which means its fields and methods are not accessible. However when you declare an array you need to specify its type : Array<Int> or Array<String> for example. This will act the same as if you had replaced all types T in the Array declaration by the type you're specifying. The type parameter is very useful in order to get strict typing of containers such as Array, List, and Tree. You can define your own parameterized classes with several type parameters for your own usage when you need it. 5.1. Constraint Parameters While it's nice to be able to define abstract parameters, it is also possible to define several constraints on them in order to be able to use them in the class implementation. For example : class EvtQueue<T : (Event, EventDispatcher)> { var evt : T; // ... }
In this class, although the field evt is a class paramater, the typer knows that it has both types Event and EventDispatcher so it can actually access it like if it was implementing both classes. Later, when an EvtQueue is created, the typer will check that the type parameter either extends or implements the two types Event and EventDispatcher. When multiple constraint parameters are defined for a single class parameter, as in the example above, they should be placed within parenthesis in order to disambiguate from cases where more class parameters are to follow. Type parameter constraints are a powerful feature that lets a developer write generic code that can be reused across different applications.
23
haXe2
6. Enums Enums are different to classes and are declared with a finite number of constructors. Here's a small example: enum Color { red; green; blue; } class Colors { static function toInt( c : Color ) : Int { return switch( c ) { case red: 0xFF0000; case green: 0x00FF00; case blue: 0x0000FF; } } }
When you want to ensure that only a fixed number of values are used then enums are the best thing since they guarantee that other values cannot be constructed. 6.1. Constructors parameters The previous Color sample shows three constant constructors for an enum. It is also possible to have parameters for constructors : enum Color2 { red; green; blue; grey( v : Int ); rgb( r : Int, g : Int, b : Int ); }
This way, there is an infinite number of Color2's possible, but there are five different constructors possible for it. The following values are all Color2: red; green; blue; grey(0); grey(128); rgb( 0x00, 0x12, 0x23 ); rgb( 0xFF, 0xAA, 0xBB );
24
haXe2 We can also have a recursive type, for example to add alpha : enum Color3 { red; green; blue; grey( v : Int ); rgb( r : Int, g : Int, b : Int ); alpha( a : Int, col : Color3 ); }
The following are valid Color3 values : alpha( 127, red ); alpha( 255, rgb(0,0,0) ); 6.2. Switch on Enum A switch has a special behavior when used on an enum. If there is no default case then it will check that all an enum's constructors are used within the switch, and if not the compiler will generate a warning. For example, consider the first Color enum : switch( c ) { case red: 0xFF0000; case green: 0x00FF00; }
This will cause a compile error warning that the constructor blue is not used. In this example you can either add a case for it or add a default case that does something. This can be quite useful, as when you add new constructors to your enum, compiler errors will alert you to areas in your program where the new constructor should be handled. 6.3. Switch with Constructor Parameters If enum constructor have parameters, they must be listed as variable names in a switch case. This way all the variables will be locally accessible in the case expression and correspond to the type of the enum constructor parameter. For example, using the Color3 enum: class Colors { static function toInt( c : Color3 ) : Int { return switch( c ) { case red: 0xFF0000; case green: 0x00FF00; case blue: 0x0000FF; case grey(v): (v << 16) | (v << 8) | v; case rgb(r,g,b): (r << 16) | (g << 8) | b; case alpha(a,c): (a << 24) | (toInt(c) & 0xFFFFFF); } } } 25
haXe2 Using switch is the only possible way to access the enum constructors parameters. 6.4. Enum Type Parameters An Enum can also have type parameters. The syntax is the same so here's a small sample of a parameterized linked List using an enum to store the cells : enum Cell<T> { empty; cons( item : T, next : Cell<T> ); } class List<T> { var head : Cell<T>; public function new() { head = empty; } public function add( item : T ) { head = cons(item,head); } public function length() : Int { return cell_length(head); } private function cell_length( c : Cell<T> ) : Int { return switch( c ) { case empty : 0; case cons(item,next): 1 + cell_length(next); } } }
Using both enums and classes together can be pretty powerful in some cases. 6.5. Using Enums as default value for parameters Because an Enum's values are in fact created from a constructor they are not constant and therefore cannot be used as default value for a parameter. However there's a simple work-around : enum MyEnum { myFirstValue; mySecondValue; }
26
haXe2
!
class Test { static function withDefaultValuesOnParameters(?a : MyEnum) { if(a == null) a = MyEnum.myFirstValue; } }
7. Packages and Imports Each file can contain several classes, enums and imports. They are all part of the package declared at the beginning of the file. If package is not declared then the default empty package is used. Each type has then a path corresponding to the package name followed by the type name. // file my/pack/C.hx package my.pack; enum E { } class C { } This file declares two types : my.pack.E and my.pack.C. It's possible to have several classes in the same file, but the type name must be unique in the whole application, so conflicts can appear if you're not using packages enough (this does not mean that you have to use long packages names everywhere). When creating types using packages, you should create a nested folder/directory structure matching the package name, and your files defining the type should be created in the innermost folder/directory. The name of the file should generally match the type you are creating. For example, to create the types E and C, in the package my.pack as shown above, your folder structure should be my\pack and the files could be E.hx and C.hx in the folder pack. In general, the name of the file is the one containing the definition of the main class. The file extension for haXe is .hx. Each part of the path in package names must begin with a lower case letter and, like all types, type names in packages must begin with an upper case letter. Hence My.Pack is an invalid package, as is my.Pack. Similarly, my.pack.e would not be a valid type name or import. 7.1. Imports Imports can be used to have access to all the types of a file without needing to specify the package name. 27
haXe2 package my.pack2; class C2 extends my.pack.C { }
Is identical to the following : package my.pack2; import my.pack.C; class C2 extends C { }
The only difference is that when using import you can use enum constructors that were declared in the my/pack/C.hx file. 7.1.1. Applying additional static methods to class instances The using keyword imports a file, additionally applying all static methods from that file as member functions on the type of their first parameter. Say we have: package my.pack; class StringUtils { public function double( string:String ):String { return string + string; } }
Then one could import the class and double a string: import my.pack.StringUtils; StringUtils.double( "Hello!"); // returns "Hello!Hello!"
Or, equivalently: using my.pack.StringUtils; "Hello!".double(); // returns "Hello!Hello!"
7.1.2. Importing a single type from a module Given a module containing multiple types, such as package my.pack2; typedef A = { a : String } typedef B = { b : String }
it is possible to import one type at a time from that module, using the following syntax: import my.pack2.A; 28
haXe2 7.1.3. Importing using wildcards ('*') Importing multiple types with wildcards is NOT supported in haXe, i.e. import flash.display.*;
will result in compiler error, quote "x.hx:1: characters x-x : Unexpected *". 7.2. Type Lookup When a type name is found, the following lookup is performed, in this order: 1
current class type parameters
2
standard types
3
types declared in the current file
4
types declared in imported files (if the searched package is empty)
5
if not found, the corresponding file is loaded and the type is searched inside it
7.3. Enum Constructors In order to use enum constructors, the file in which the enum is declared must first be imported, or you can use the full type path to access constructors as if they were static fields of the enum type. var c : my.pack.Color = my.pack.Color.red;
As an exception, in switch: if the type of the enum is known at compile time, then you can use constructors directly without the need to import.
29
haXe2
8. Dynamic Disclaimer: Dynamic variables are often necessary to ensure interoperability with third party or platform dependent libraries, or for very specific cases where dynamic variables are absolutely required. Their use is not encouraged for normal use, as their behavior differs between targets. When you want to get some dynamically typed behavior and break free from the type system, you can use the Dynamic type which can be used in place of any type without any compile-time type-checking: var x : Dynamic = ""; x = true; x = 1.744; x = {}; x.foo = 'bar'; x = new Array();
When a variable is marked "Dynamic" the compiler does not ensure that types are followed for a given variable. This means that it is possible to switch from referencing one type of object to another. Also, any assignment, field assignment, or index access will succeed at compile time. At run time however, different targets may not recognize or even allow certain operations on certain variables. For instance, on the flash9 (AVM2 target) a dynamic variable is assigned a value, such as an integer or boolean an exception will be thrown. On less strict platforms (such as javascript) an error may not be thrown, but the value will not be saved. var x:Dynamic = 4; x.foo = "Bar"; //flash9: Number.
ReferenceError: Error #1056: Cannot create property foo on
//JS: foo.x = null;
Having an object 'implement Dynamic', allowing fields to be added/removed at runtime, is quite a different concept. See 'Implementing Dynamic' below. Trying to modify the fields of a flash9 MovieClip instance will work (since it itself implements a Dynamic type), but modifying the fields of a flash9 Sprite class will cause a run time reference error: // flash9 var m:Dynamic = new flash.display.MovieClip(); m.foo = "Bar"; trace(m.foo); // value is saved var s:Dynamic = new flash.display.Sprite(); s.foo = "Bar"; // ReferenceError!
30
haXe2 In short, "Dynamic" variables never check for type by the compiler, whether they are assigned in a method body, or as a method argument. But, they still will observe runtime type restrictions that are in place for a given target, and may cause run-time errors. An untyped variable is of type Unknown and not Dynamic. That is, an untyped variable does not have a type until it is determined by type inference. A Dynamic variable has a specific type: an any type. 8.1. Parameterized Dynamic Variables Adding a parameter to a Dynamic type allows arbitrary access and assignment to any field as long as it is the same type as the parameter. For instance, Dynamic<String> allows access or assignment to any field, as long as these accesses accept or produce String types. This is useful to encode Hashtables where items are accessed using dot syntax: var att : Dynamic<String> = xml.attributes; att.name = "Nicolas"; att.age = "26"; // ...
or var foo : Dynamic<String> = cast {}; foo.name = "Nicolas";
8.2. Implementing Dynamic Any class can also implement Dynamic with or without a type parameter. In the first case, the class fields are typed when they exist, otherwise they have a dynamic type: class C implements Dynamic<Int> { public var name : String; public var address : String; } // ... var c = new C(); var n : String = c.name; // ok var a : String = c.address; // ok var i : Int = c.phone; // ok : use Dynamic var c : String = c.country; // ERROR // c.country is an Int because of Dynamic<Int>
Dynamic behavior is inherited by subclasses. When several classes are implementing different Dynamic types in a class hierarchy, the last Dynamic definition is used. In the case of accessing an undefined property on an object which implements Dynamic the function "resolve" will be called. This function is called with one parameter, the property attempted to be accessed, and can return any value.
31
haXe2 8.3. Type Casting You can cast from one type to another by using the cast keyword. var a : A = .... var b : B = cast(a,B);
This will either return the value a with the type B if a is an instance of B or it will throw an exception "Class cast error". 8.4. Untyped One other way to do dynamic things is to use the untyped keyword. When an expression is untyped, no type-check will be done so you can execute many dynamic operations at once: untyped { a["hello"] = 0; }
Be careful to use untyped expressions only when you really need them and when you know what you're doing. 8.5. Unsafe Cast The untyped keyword provides great flexibility at the cost of allowing potentially invalid syntax on the right side of the untyped keyword. It also allows for an unsafe cast which is similar to the standard cast except that no type is specified. That means that the cast call will not result in any runtime check, but will allow you to "lose" one type. var y : B = cast 0;
An unsafe cast is similar to storing a value in a temporary Dynamic variable : var tmp : Dynamic = 0; var y : B = tmp;
32
haXe2
9. Advanced Types Up to now, we have seen the following types : 1
class instance
2
enum instance
3
dynamic
There are several additional important types. 9.1. Anonymous An anonymous type is the type of an anonymously declared object. It is also the type of a Class identifier (corresponding to all the static fields) or an Enum identifier (listing all the constructors). Here's an example that shows it: enum State { on; off; disable; } class C { static var x : Int; static var y : String; function f() { // print { id : Int, city : String } type({ id : 125, city : "Kyoto" }); } function g() { // print { on : State, off : State, disable : State } type(State); } function h() { // print { x : Int, y : String } type(C); } }
9.2. Typedef You can define type definitions which are a kind of type shortcut that can be used to give a name to an anonymous type or a long type that you don't want to repeat everywhere in your program : typedef User = { var age : Int; var name : String; } // .... 33
haXe2 var u : User = { age : 26, name : "Tom" };
// PointCube is a 3-dimensional array of points typedef PointCube = Array<Array<Array<Point>>>
Typedefs are not classes, they are only used for typing. 9.2.1. As aliases You can "alias" types using type definitions in the following manner: class MyClassWithVeryLongName { } typedef MyClass = MyClassWithVeryLongName;
You can assign any kind of type like this, not just classes - enums, interfaces and other type definitions can be aliased as well in the similiar manner. The resulting type identifiers can be used wherever you would otherwise be using the class, enum, or whatever. 9.2.2. Private members Typedefs can contain private members in their definitions. This allows access to otherwise unavailable members. class Foo { public function new() {} public function fullAccess() return true function restrictedAccess() return true } typedef Bar = { function fullAccess():Bool; private function restrictedAccess():Bool; } class Main { public static function main() { var foo = new Foo(); foo.fullAccess(); //Compiler Error: Cannot access to private //field restrictedAccess foo.restrictedAccess(); var bar:Bar = foo; bar.fullAccess(); //No Error, this is allowed bar.restrictedAccess(); } }
34
haXe2 9.2.3. Private types A type - class, typedef, interface or enum - can be defined as 'private', which will confine its visibility only to the module it is defined in. Public types belong to packages they are defined in, and they can only be defined once. With private type definitions however you can have a "local" type only visible within the module it is defined in. Another private type with the same name and in the same package can be defined in another module. private typedef MyType = { }
9.2.4. Accessing Typedefs From Other Packages It is often useful to use a typedef from another package for a variable declaration. This can be accomplished in the same ways that internal helper classes are referenced. For a given typedef "MyType": // in Foo.hx private typedef MyType = { ... }
The first way is to simply import the Class containing the Typedef you wish to use: // in Bar.hx import Foo; class Bar{ var mt:MyType; }
The second way is to use a special extended package definition, which allows you to avoid using import: // in Bar.hx class Bar{ var mt:Foo.MyType; }
9.3. Functions When you want to define function types to use them as variables, you can define them by listing the arguments followed by the return type and separated with arrows. For example Int -> Void is the type of a function taking an Int as argument and returning Void. Color -> Color ->Int takes two Color arguments and returns an Int. class C { function f(x : String) : Int { // ... } function g() { 35
haXe2 type(f); // print String -> Int var ftype : String -> String = f; // ERROR : should be String -> Int } }
9.4. Unknown When a type is not declared, it is used with the type Unknown. The first time it is used with another type, it will change to it. This was explained in more details in type inference. The id printed with the Unknown type is used to differentiate several unknowns when printing a complex type. function f() { var x; type(x); // print Unknown<0> x = 0; type(x); // print Int }
The diversity of types expressible with haXe enable more powerful models of programming by providing high-level abstractions that don't need complex classes relationships to be used. 9.5. Extensions Extensions can be used to extend either a typedef representing an anonymous type or to extend a class on-the-fly. Here's an example of anonymous typedef extension : typedef Point = { var x : Int; var y : Int; } // define 'p' as a Point with an additional field z var p : {> Point, z : Int } p = { x : 0, y : 0, z : 0 }; // works p = { x : 0, y : 0 }; // fails
For classes, since they don't define types, you need to use a cast when assigning, it's unsafe so be careful : var p : {> flash.MovieClip, tf : flash.TextField }; p = flash.Lib._root; // fails p = cast flash.Lib._root; // works, but no typecheck !
36
haXe2 You can also use extensions to create cascading typedefs : typedef var var } typedef var }
Point = { x : Int; y : Int; Point3D = {> Point, z : Int;
In that case, every Point3D will of course be a Point as well. Cascading typedefs can be created from normal class types as well, but the compiler will not be able to match this typedef against an anonymous object, or any other instanced class. This is because haXe is not fully structurally subtyped. Structural subtyping can only occur between anonymous typedefs or between an instance and an anonymous typedef.
37
haXe2
10. Iterators An iterator is an object which follows the Iterator typedef (The type T is the iterated type) : typedef Iterator<T> = { function hasNext() : Bool; function next() : T; }
You can use the for syntax in order to execute iterators. The simplest iterator is the IntIter iterator which can easily be built using the operator ... (three dots). For example this will list all numbers from 0 to 9 : for( i in 0...10 ) { // ... }
Or the usual for loop : for( i in 0...arr.length ) { foo(arr[i]); }
You don't need to declare the variable i before using a for, since it will be automatically declared. This variable will only be available inside the for loop and cannot be modified within the loop. 10.1. Implementing Iterator You can also define you own iterators. You can simply follow the Iterator typedef in your class by implementing the hasNext and next methods. Here is for example the IntIter class that is part of the standard library : class IntIter { var min : Int; var max : Int; public function new( min : Int, max : Int ) { this.min = min; this.max = max; } public function hasNext() { return( min < max ); } public function next() { return min++; } }
38
haXe2 Once your iterator is implemented, you can simply use it with the for...in syntax, this way : var iter = new IntIter(0,10); for( i in iter ) { // ... }
The variable name in the for is automatically declared and its type is bound to the iterator type. It is not accessible after the iteration is done. 10.2. Iterable Objects If an object has a method iterator() taking no arguments and returning an iterator, it is said to be iterable. It doesn't have to implement any type. You can use a class directly in a for expression without the need to call the iterator() method: var a : Array<String> = ["hello","world","I","love","haXe","!"]; for( txt in a ) { tf.text += txt + " "; }
This sample will build the string by listing an array element using an iterator. It is the same as calling a.iterator() in the for expression.
39
haXe2
11. Properties Properties are a specific way to declare class fields, and can be used to implement several kinds of features such as read-only/write-only fields or fields accessed through getter-setter methods. Here's a property declaration example : class C { public var x(getter,setter) : Int; }
The values for getter and setter can be one of the following : 1
a method name that will be used as getter/setter
2
null if the access is restricted from anywhere but its class' methods (see more below)
3
default if the access is a classic field access
4
dynamic if the access is done through a runtime-generated method
5
never if the access is never allowed, not even by means of Reflection
11.1. Sample Here's a complete example : class C { public public public public
var var var var
ro(default,null) : Int; wo(null,default) : Int; x(getX,setX) : Int; y(getX,never) : Int; // here y should always equal x
private var my_x : Int; private function getX():Int { return my_x; } private function setX( v : Int ):Int { if( v >= 0 ) my_x = v; return my_x; } }
40
haXe2 Using property declarations, we declare four public fields in the class C : 1
outside the class code, the field ro is read only
2
outside the class code, the field wo is write only
3
the field x is accessed through a pair of getter/setter methods
4
the field y can never be set, not even from code within the same class
For instance, the following two functions are equivalent, although the methods getX and setX are private and then can't be accessed directly as in f2 : var c : C; function f1() { c.x *= 2; } function f2() { c.setX(c.getX()*2); }
11.2. Important Remarks It's important to know that such features only work if the type of the class is known. There is no runtime properties handling, so for instance the following code will always trace null since the method getX will never be called : var c : Dynamic = new C(); trace(c.x);
The same goes for read-only and write-only properties. They can always be modified if the type of the class is unknown. Also, please note that you have to return a value from the setter function. The compiler will complain otherwise. 11.3. Dynamic Property The additional dynamic access can be used to add methods at runtime, it's quite a specific feature that should be used with care. When a dynamic field x is accessed for reading, the get_x method is called, when accessed for writing the set_x method is called : class C { public var age(dynamic,dynamic) : Int; public function new() { } } class Test { static function main() { var c = new C(); var my_age = 26; 41
haXe2 Reflect.setField(c,"get_age",function() { return my_age; }); Reflect.setField(c,"set_age",function(a) { my_age = a; return my_age; }); trace(c.age); // 26 c.age++; // will call c.set_age(c.get_age()+1) trace(c.age); // 27 } }
11.4. Null Property Unlike initial expectations, null setter specification does not deny all access to the property, as is evidenced with the following example, which indeed succeeds to set the propertys value from within any class' own method(s): class Main { static function main() { var foo = new Foo(); /// Naturally, fails - write access denied from here. foo.prop = 1; Foo.set_prop_value(foo, 1); /// Succeeds. } } class Foo { static public function set_prop_value(foo: Foo, value: Int) { /// Will actually set the value of 'prop' indeed. foo.prop = value; } public var prop(default, null): Int; public function new() { } }
So, unlike specifying the setter as 'never' (more on that below), 'null' only restricts access from outside the class definition scope, i.e. all scopes eligible for "public" access only. If you need strictly read-only properties, regardless access context, use 'never'. 11. 5. Never Property This property non-access specifier can be useful in a number of cases. Below is an example where the number of stored List items is returned by the numItems property. Of course, the return value should always be read from the List and never be set explicitly..
42
haXe2 class Container { private var items : List<Item>; public var numItems( getNumItems, never ) : Int; function getNumItems() : Int { return items.length; } public function new() { items = new List(); items.add( new Item() ); items.add( new Item() ); } }
Now, if called after adding the items in the constructor, both numItems = 3; and Reflect.setField(this, "numItems", 3); would fail because of the never access specified. On the other hand, if null was specified instead of never, the compiler would have allowed the Reflect call. This could mislead the developer into property abuse.
43
haXe2
12. Optional Arguments Some function parameters can be made optional by using a question mark ? before the parameter name : class Test { static function foo( x : Int, ?y : Int ) { trace(x+","+y); } static function main() { foo(1,2); // trace 1,2 foo(3); // trace 3,null } }
12.1. Default values When not specified, an optional parameter will have the default value null. It's also possible to define another default value, by using the following syntax : static function foo( x : Int, ?y : Int = 5 ) { // ... }
Thanks to type inference, you can also shorten the syntax to the following : static function foo( x : Int, y = 5 ) { // ... }
12.2. Ordering Although it is advised to put optional parameters at the end of the function parameters, you can use them in the beginning or in the middle also. Also, optional parameters are independent in haXe. It means that one optional parameter can be used without providing a previous one : function foo( ?x : A, ?y : B ) { } foo(new A()); // same as foo(new A(),null); foo(new B()); // same as foo(null, new B()); foo(); // same as foo(null,null); foo(new C()); // compile-time error foo(new B(),new A()); // error : the order must be preserved
However, such usages of optional arguments could be considered quite advanced.
44
haXe2 12.3. Enum Values Enums' values can not be used as default values for optional arguments. See Enums for more information. 12.4. Warning Optional Arguments are a compile-time feature and therefore their behavior may vary on different platforms when used on Dynamic calls or with Reflection.
45
haXe2
13. Conditional Compilation Sometimes you might want to have a single library using specific API depending on the platform it is compiled on. At some other time, you might want to do some optimizations only if you turn a flag ON. For all that, you can use conditional compilation macros (AKA preprocessor macros): Here's an example of multiplaform code: #if flash8 // haXe code specific for flash player 8 #elseif flash // haXe code specific for flash platform (any version) #elseif js // haXe code specific for javascript plaform #elseif neko // haXe code specific for neko plaform #else // do something else #error // will display an error "Not implemented on this platform" #end
There is also a not prefix, which is the exclamation mark. So for a block that is compiled for every plaform except for PHP, you can do: #if !php // This code is not for PHP! #end
In addition to the various platform defines, you can also detect if this is a debugenabled build (set using -debug): #if debug trace("Debug infos for all debug compiles"); #end
You can define your own variables by using the haXe compiler commandline options (for example using -D mydebug): #if mydebug trace("Some personalized debug infos"); #end
Logical and/or operators are also supported: #if (neko && mydebug) // Only for "mydebug" mode on Neko #end #if (flash || php) // Code that works for either flash or PHP #end 46
haXe2
14. Inline The inline keyword can be used in two ways: for static variables and for any kind of method. 14.1. Inlining Static Variables For static variables, it's pretty simple. Every time the variable is used, its value will be used instead of the variable access. Example : class Test { static inline var WIDTH = 500; static function main() { trace(WIDTH); } }
Using "inline" adds a few restrictions : 1
the variable must be initialized when declared
2
the variable value cannot be modified
The main advantage of using "inline" is that you can use as many variables as you want without slowing down your code with these variables accesses since the value is directly replaced in the compiled/generated code. 14.2. Inlining Methods The principle is the same for a method. The less expensive function call is the one that is never done. In order to achieve that for small methods that are often called, you can "inline" the method body a the place the method is called. Let's have a look at one example : class Point { public var x : Float; public var y : Float; public function new(x,y) { this.x = x; this.y = y; } public inline function add(x2,y2) { return new Point(x+x2,y+y2); } } class Main { static function main() { var p = new Point(1,2); var p2 = p.add(2,3); // is the same as writing : var p2 = new Point(p.x+2,p.y+3); } }
Again, there's some limitations to inline functions : 47
haXe2 1
they cannot be redefined at runtime
2
they cannot be overridden in subclasses
3
an function containing "super" accesses or declare another function cannot be inlined
4
if the inline function has arguments, then the arguments evaluation order is undefined and some arguments might even be not evaluated, which is good unless they have some expected side-effects
5
if the inline returns a value, then only "final returns" are accepted, for example :
inline function foo(flag) { return flag?0:1; } // accepted inline function bar(flag) { if( flag ) return 0; else return 1; } // accepted inline function baz(flag) { if( flag ) return 0; return 1; } // refused
Apart from these few restrictions, using inline increases the compiled/generated codesize but brings a lot of additional speed for small methods. Please note that it's also possible to inline static methods.
48
haXe2
15. Keywords
1 2 3 4 5 6 7 8 9
break Exit a loop immediately. callback Curries a function call. Useful for passing functions. case Specifies an entry in a switch statement. cast Attempt to get a value as a different type. Can also be used to indicate that a class' field can be dynamically overridden. catch Declares code that is called when an exception occurs. class Declares a class. continue Skip to the next loop iteration. default If used in a switch statement, default is used to indicate a case that will be executed if none of the other cases were executed. If used in a property definition, default indicates that the classic field access are available. do Declares a "do-while" loop. do expr-loop while( expr-cond );
10 dynamic
Used in a property definition to indicate that access will be set at runtime. Also used as a flag in a class variable or method declaration to indicate that the field may be dynamically rebound.
11 else
Declares a block to be executed if the condition is not true. if( expr-cond ) expr-1 [else expr-2]
12 enum
Declares an enum.
49
haXe2
13 extends
Declares a class to be a subclass of another class.
14 extern
Indicates that the class is already defined.
15 false
A boolean value that evaluates to false.
16 for
Declares a "for" loop. for( variable in iterable ) expr-loop;
17 function
Declares a local function or method of a class.
18 here
Expands to a haxe.PosInfos structure.
19 if
Declares a block to be executed if a condition is true. if( expr-cond ) expr-1 [else expr-2]
20 implements
Declares the interfaces implemented by a class.
21 import
Indicates that the class references a class from a different package is reference from the file.
22 in
Part of the syntax of the "for" loop.
23 inline
Indicates that the compiler can speed up the code by replacing references to a function or static variable with the method body or variable's value, respectively.
24 interface
Declares an interface.
25 never
Used in a property definition to indicate that access is not available.
26 new
Creates an object by calling a class' constructor.
50
haXe2
27 null
If used in a comparison or assignment, null is variable without a value. If used in a property definition, null indicates that access is only allowed from within the class.
28 override
Indicates that a class' field is overriding its parent class.
29 package
Packages can be used to group related classes.
30 private
Indicates that access to field is only allowed from the class.
31 public
Indicates that a field's access not restricted.
32 return
Exit a function before the end or return a value from a function.
33 static
Declares a class member.
34 super
Used by a class to call a method from its superclass.
35 switch
Equivalent to multiple if...else if... tests on the same value.
36 this
Used by a class to refer to the current object.
37 throw
Throws an exception.
38 trace
A global function to ease debugging. Outputs the given variable as a String. trace("Will be shown when compiled for debugging");
39 true
A boolean value that evaluates to true.
40 try
A try block is code where exception handlers can be defined.
51
haXe2
41 typedef
Gives a name to an anonymous type.
42 untyped
Prevent type checking for a block of code.
43 using
Allows an static functions to be called as if they were an object's methods.
44 var
Declares a local or class variable.
45 while
Declares a "while" loop. while( expr-cond ) expr-loop;
52