Система типов в Haxe
HaXe - объектно-ориентированный язык. Однако, в отличие от некоторых других языков, Haxe не "чистый" объектно-ориентированный язык, в том смысле что не всё в Haxe является объектом. Вместо этого Haxe продвигает идею что различные типы несут больше смысла чем один божественный тип Object.
Haxe разделяет типы в несколько "типовых групп". Эти группы приведены ниже:
Экземпляры Класса
Подобно многим объектно-ориентированным языкам, наиболее обширно используемый тип в Haxe это экземпляр класса.
Класс можно объявить с помощью ключевого слова "class":
class Point { var x : Int; var y : Int; public function new(x,y) { this.x = x; this.y = y; } public function toString() { return "Point("+x+","+y+")"; } }
В этом примере класс имеет конструктор (функция "new"), который вызывается как только экземпляр создан, два поля "x" и "y" с типом "Int", а так же единственный метод "toString" который будет вызван когда мы захотим отобразить на экране представление экземпляра класса.
Методы в Haxe ссылаются по имени, поэтому вы можете создавать только один метод или поле с данным именем - невозможно использовать одно и тоже имя для разных методов в одном классе.
Вы можете создать экземпляр данного класса используя ключевое слово "new".
var p = new Point(-1,65); trace(p); // вызовет p.toString() и отобразит результат на экране
В этом примере, тип переменной "p" - "Point" является сокращением для фразы экземпляр класса "Point"
Наследование
Один класс может наследовать другой посредством ключевого слова "extends". Подкласс не может переопределять одноимённые поля, но может перекрывать методы если при этом не изменяется тип метода:
class Point3D extends Point { var z : Int; public function new(x,y,z) { super(x,y); this.z = z; } override function toString() { return "Point3D("+x+","+y+","+z+")"; } }
Когда класса наследует другой класс он становится подтипом родительского класса. Это означает, что каждый экземпляр "Point3D" также является и экземпляром типа "Point", как показывает следующие пример:
var p : Point = new Point3D(0,0,0);
Интерфейсы
Класс так же может реализовывать один или несколько интерфейсов, которые могут детализировать которые переменные и/или методы данный класс должен содержать или реализовывать.
interface IsPrintable { function toString() : String; } interface HasX { var x : Int; } class AnotherPoint extends Point, implements IsPrintable, implements HasX { }
Поскольку "AnotherPoint" наследует "Point" то так же содержит и поле "x", которое требуется для "HasX" интерфейс и содержит "toString" метод, который требуется для интерфейса "IsPrintable". Нет нужды объявлять поля и методы требуемые в интерфейсе, но возможно объявлять/определять дополнительные поля и функции по мере необходимости.
Интерфейсные типы так же могут быть использованы как подтипы, для экземпляра:
var p : IsPrintable = new AnotherPoint(); // OK
Это допустимо для компилятора Haxe, потому что "AnotherPoint" реализует "IsPrintable", в то время как следующий пример возбуждает ошибку:
var p : IsPrintable = new Point(); // ОШИБКА
Этот пример возбуждает ошибку потому что класс "Point" не реализует интерфейс "IsPrintable" ни напрямую и опосредованно.
Statics
Properties
Структура
In addition to classes, Haxe also provides a structure type :
var p = { x : -1, y : 65 };
In this example, the type of the variable p will be the structure { x : Int, y : Int }, consisting of two fields x and y, both of type Int.
Structural Subtyping
Structures use structural subtyping rules, so the Haxe compiler will check that all fields declared are present, and display an error if a field is missing :
var p : { x : Int, y : Int }; // declare a typed variable p = { x : -1, y : 65 }; // OK p = { x : -1 }; // ERROR : field y is missing
But using structural subtyping also means that you can hide extra fields, in which case you will not be able to access them anymore :
p = { x : -1, y : 65, z : 45 }; // OK, hiding field z trace(p.z); // ERROR, p has no field z
Structures do not just allow structural subtyping between themselves - you can also bind a class instance to structure variable :
p = new Point(-1,65);
The following will work, since the instance of the class Point has both x : Int and y : Int fields.
Structures are a perfect way to define some common traits between otherwise unrelated classes. In many languages, this is done through interfaces. Haxe also has interfaces which can be used for that, but it requires you to carefully design your application structure in order to ensure that good interfaces are defined.
Structures can act as "implicit interfaces" since the original class does not need to be modified in order to be able to be used as a structure.
For example the following method will accept all class instances and structures which have two fields x and y, each of type Int :
function add( s : { x : Int, y : Int } ) { return s.x + s.y; }
Methods
Enum Instance
Function
Typedef
Dynamic
Unknown
Abstract
An abstract type is only defined by a name, but can have subtype relationships with other abtract types.
Example :
typedef Void; // defines an abstract type "Void" typedef Float; // defines an abstract type "Float" typedef Int < Float; // defines an abstract type "Int" // which is also a subtype of Float, // meaning that every Int is also a Float
Note : Abstract types are not yet part of the language syntax, so you can't define your own abstract types this way (as of haxe 2.03), although this should be included in future releases. Currently, Int and Float are defined as classes and Void as an empty enum