Даниил Коростелёв (@nadako)
Haxe - язык программирования общего назначения с простым синтаксисом, мощной системой типов и богатыми возможностями метапрограммирования.
class Greeter {
var who:String;
public function new(who) {
this.who = who;
}
public function greet() {
trace('Hello, $who!');
}
}
Компилятор Haxe ориентирован на генерацию кода для разных рантаймов
Парсинг исходника в AST
Типизация AST (проверка и вывод типов)
Анализ и оптимизация AST
Генерация таргет-кода
(js,c++,c#,etc)
Общее для всех целевых платформ
Чтобы это работало, Haxe минимально специфицирует поведение в рантайме и при этом предоставляет мощные средства для того, чтобы перенести максимум кода в этап компиляции. Дополнительный плюс этого подхода в том что мы получаем на выходе более компактный и производительный код для рантайма.
Отсюда принцип: минимизируйте использование рантайм-рефлекшена и динамической работы с типами.
// простейший вид алгебраического типа - привычное многим перечисление
enum ResourceType {
Gold;
Wood;
Iron;
}
// полноценный алгебраический тип
enum Reward {
Experience(amount:Int); // конструктор с одним аргументом
Resource(type:ResourceType, amount:Int); // ... с несколькими
Chest(rewards:Array<Reward>); // с собой же в качестве аргумента
PremiumStatus; // без аргументов
}
aka сопоставление по образцу
Механизм для компактного выражения условий ветвления по значению как простых так и сложных данных.
switch (httpCode) {
case 200:
processData();
case 404:
handleNotFound();
}
// свитч по полям объекта
switch (player) {
// поздравляем Татьян 15-го уровня
case {level: 15, name: "Tanya"}:
sendCongratulations();
// отправляем подарок игрокам 10-го уровня, причем передавая
// значение isPremium, захваченное в локальную переменную `p`
case {level: 10, isPremium: p}:
giveLevelTenReward(p);
}
Простой switch по константам
Switch по полям объекта
function getSellCost(reward:Reward):Int {
switch (reward) {
// премиум статус и опыт не продаётся
// (игнорируем значение Experience с помощью _)
case PremiumStatus | Experience(_):
return 0;
// золото стоит дороже других ресурсов
case Resource(Gold, amount):
return amount * 10;
// другие ресурсы продаются по монете за единицу
case Resource(other, amount):
return amount;
// сундук стоит столько, на сколько в нём лежит наград
case Chest(rewards):
var sum = 0;
for (reward in rewards)
sum += getSellCost(reward);
return sum;
}
}
Switch по enum с захватом значений
abstract UserId(Int) {
function new(value) {
this = value;
}
public static function generate():UserId {
return new UserId(42);
}
}
abstract Money(Int) {
public function new(value) {
this = value;
}
public function format():String {
return 'USD $this';
}
}
class Main {
static function giveMoney(user:UserId, money:Money) {
trace('Giving ${money.format()} to user $user');
}
static function main() {
var user = UserId.generate();
var money = new Money(50);
giveMoney(user, money);
// Ошибка компиляции: Money should be UserId
// giveMoney(money, user);
}
}
пример
пример
// создаём анонимный объект, его тип будет выведен автоматичеки
var person = {name: "Dan", age: 29};
// тип анонимной структуры можно указать и явно
function greet(p:{name:String, age:Int}) {
trace('Hello ${p.name}, I hope you are much younger than ${p.age}!');
}
// необходимые поля присутствуют и их типы соответствуют,
// а значит такой вызов будет валиден
greet(person);
// почему бы и нет
typedef Name = String
// имя для анонимной структуры
typedef Person = {
var name:String;
var age:Int;
}
// для тех кто не любит ASCII art
typedef AsyncResult<T> = Promise<Either<Error<String>,Result<T>>>
extern class Buffer {
var length:Int;
function readInt8(offset:Int):Int;
function writeInt8(value:Int, offset:Int):Void;
static function alloc(size:Int):Buffer;
}
@:include("SDL.h")
extern class Sdl {
@:native("SDL_Init")
static function init(flags:Int):Int;
@:native("SDL_CreateWindow")
static function createWindow(title:cpp.ConstCharStar, x:Int, y:Int, w:Int, h:Int, flags:Int):cpp.RawPointer<SdlWindow>;
}
extern class SdlWindow {}
примеры
JavaScript (node.js)
C++ (SDL)
aka макросы
Как результат: потенциально более простой и безопасный исходный код, а так же более компактный и быстрый сгенерированный код.
Три вида макросов
В общем...
Макросы - крайне мощная штука, позволяющая автоматизировать много задач и исходный сделать код компактнее и безопаснее.
Однако чтобы её эффективно использовать, нужно хорошо понимать как компилятор работает с кодом.
Кроме того, обилие макросов может наоборот усложнить вашу кодовую базу, ведь макро-код находится в другой области понимания, нежели код который вы пишете непосредственно для вашего приложения. Поэтому, при изучении макросов важно научиться не злоупотреблять ими и применять только там где они действительно нужны.
Совместно с оптимизациями, производимыми компиляторами и JIT-анализаторами целевых платформ получаем хорошее быстродействие и небольшой размер даже для "тяжелого" кода.
(актуально только если ваш код хорошо типизирован и не злоупотребляет рефлекшеном)
Что конкретно:
пример-упражнение
class Life {
public var value:Int;
public inline function new(v) {
this.value = v;
}
}
class Main {
inline static function getQuestion(life) {
return {
life: life.value,
universe: life.value + 6,
everything: life.value * 2
};
}
inline static function getAnswer(question, callback) {
var realQuestion = question.everything;
if (realQuestion > 9)
callback(realQuestion + 12);
else
callback(13);
}
static function main() {
var life = new Life(15);
var question = getQuestion(life);
getAnswer(question, function(answer) {
trace('The answer is $answer');
});
}
}
(function () { "use strict";
var Main = function() { };
Main.main = function() {
console.log("The answer is " + 42);
};
Main.main();
})();
Исходный код
Генерируемый код (JS)
Текущий фокус:
официальные и общепризнанные
вопросы?