Даниил Коростелёв (@nadako)

Что такое Haxe?

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-way

  • Haxе генерирует код максимально близкий к родному для платформы, без лишних врапперов
  • Все платформы разные и имеют свои особенности
  • Haxe позволяет абстрагироваться над платформами

Чтобы это работало, Haxe минимально специфицирует поведение в рантайме и при этом предоставляет мощные средства для того, чтобы перенести максимум кода в этап компиляции. Дополнительный плюс этого подхода в том что мы получаем на выходе более компактный и производительный код для рантайма.

Отсюда принцип: минимизируйте использование рантайм-рефлекшена и динамической работы с типами.

Киллер-фичи языка

  • Переменные!
  • Условия!
  • Циклы!
  • Функции!
  • Классы!
  • ...и даже интерфейсы!

Алгебраические типы

  • Создается ключевым словом enum
  • Описывает набор конструкторов значений с параметрами (или без)
  • Удобны для описания составных данных, содержащих разный набор данных в зависимости от конструктора
// простейший вид алгебраического типа - привычное многим перечисление
enum ResourceType {
    Gold;
    Wood;
    Iron;
}

// полноценный алгебраический тип
enum Reward {
    Experience(amount:Int); // конструктор с одним аргументом
    Resource(type:ResourceType, amount:Int); // ... с несколькими
    Chest(rewards:Array<Reward>); // с собой же в качестве аргумента
    PremiumStatus; // без аргументов
}

Паттерн-матчинг

aka сопоставление по образцу

  • Используется в операторе switch
  • Гораздо мощнее switch в Си-подобных языках (и не требует break)
  • Каждый case описывает шаблон, в котором можно проверить одно или несколько значений из составных объектов
  • Кроме проверки, также можно захватывать значения в локальные переменные
  • Является основным способом работы с enum, но также работает с любыми объектами и даже массивами.

Механизм для компактного выражения условий ветвления по значению как простых так и сложных данных.

Паттерн-матчинг

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);
    }
}

пример

Структурная типизация

  • Представлена особым видом типов - "анонимными структурами", описывающими только набор полей объекта
  • Предоставляет возможность легко создать анонимный объект, даже не описывая его тип
  • Как объекты классов так и анонимные объекты могут быть присвоены переменной структурного типа, если их структура (набор полей и их типов) подходит под требования типа
  • Хорошо подходит для описания JSON-данных и для быстрого прототипирования
  • На статических платформах доступ к полям структур имеет оверхед по сравнению с классами из-за необходимости динамического lookup'а. Это стоит учитывать.

Структурная типизация

пример

// создаём анонимный объект, его тип будет выведен автоматичеки
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

  • Просто "псевдоним" для типа
  • Часто используется с анонимными структурами, чтобы дать им имя и повторно использовать
  • Поддерживает параметризацию типа
  • Может быть использован для любого типа
// почему бы и нет
typedef Name = String

// имя для анонимной структуры
typedef Person = {
    var name:String;
    var age:Int;
}

// для тех кто не любит ASCII art
typedef AsyncResult<T> = Promise<Either<Error<String>,Result<T>>>

Extern

  • Описывается как haxe-класс с модификатором extern
  • Позволяет взаимодействовать с нативным (для целевой платформы) кодом
  • Не требует дополнительного кода-клея
  • Не требует особого структурирования нативного кода и имеет средства для описания практически любого внешнего кода
  • Для некоторых платформ, таких как Flash, C# и Java, компилятор Haxe может сам загружать extern'ы напрямую из байткода (swf, dll, jar)
  • Для популярных библиотек и фреймворков есть готовые наборы extern'ов, но даже если для вашей любимой нет - не беда, просто опишите минимальный extern сами и дополняйте по мере надобности

Extern

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 макросы

  • Haxe-код интерпретируемый во время компиляции
  • Имеют доступ к типам и выражениям языка через API компилятора
  • Позволяет генерировать код и выполнять дополнительные проверки во время компиляции

 

Как результат: потенциально более простой и безопасный исходный код, а так же более компактный и быстрый сгенерированный код.

Метапрограммирование

Три вида макросов

  • Макро-методы (aka expression macro) - получают на вход выражения, возвращащают выражения, подставляемое на место вызова. Удобно для синтаксического сахара.
  • Build-макросы - программно генерируют целые новые типы (class, abstract, enum, typedef). Удобно для создания разного рода врапперов и прокси, а так же генерации типов на основе внешних данных (из файла, из сети и т.п.)
  • Init/onGenerate-макросы - макросы, запускаемые в начале или вконце компиляции. Удобно для подготовительных или дополнительных работ (доп.проверки, сбор данных).

Метапрограммирование

В общем...

Макросы - крайне мощная штука, позволяющая автоматизировать много задач и исходный сделать код компактнее и безопаснее.

Однако чтобы её эффективно использовать, нужно хорошо понимать как компилятор работает с кодом.

Кроме того, обилие макросов может наоборот усложнить вашу кодовую базу, ведь макро-код находится в другой области понимания, нежели код который вы пишете непосредственно для вашего приложения. Поэтому, при изучении макросов важно научиться не злоупотреблять ими и применять только там где они действительно нужны.

Оптимизации

  • Haxe - оптимизирующий компилятор
  • Информация о типах во время компиляции позволяет проводить мощный статический анализ и оптимизацию кода на этапе сборки

 

Совместно с оптимизациями, производимыми компиляторами и JIT-анализаторами целевых платформ получаем хорошее быстродействие и небольшой размер даже для "тяжелого" кода.

 

(актуально только если ваш код хорошо типизирован и не злоупотребляет рефлекшеном)

Оптимизации

Что конкретно:

  • Инлайнинг функций
  • Инлайнинг объектов
  • Удаление неиспользуемых полей и классов (dead code elimination)
  • Анализ и оптимизация выражений
    • const propagation
    • copy propagation
    • expression fusion
    • local dead code elimination
    • purity inference
    • code motion

Оптимизации

пример-упражнение

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)

Сообщество

  • Команда Haxe - люди из разных стран и отраслей IT-отрасли
  • Haxe - открытый проект, разрабатываемый через GitHub
  • Работают принципы любого open-source проекта:
    • нашел проблему - заведи issue
    • знаешь как улучшить - создай pull request
    • хочешь помочь - добро пожаловать, работа найдётся для всех :)
  • Доброжелательное и знающее сообщество - не стесняйтесь задавать вопросы в Google Groups
  • Haxe Foundation - организация для коммерческого и стратегического партнёрства с фирмами

Ближайшие планы

  • Язык и компилятор Haxe в хорошем состоянии
  • Большинство обсуждаемых изменений в языке - ломают совместимость, поэтому до версии 4 вводиться не будут
  • Багфиксы, оптимизации и дополнения стандартной библиотеки идут своим чередом

 

Текущий фокус:

  • Инфраструктура
    • менеджер библиотек haxelib
    • документация (manual, cookbook)
    • упрощение поддержки для IDE
  • Формализация процессов (contributor guidelines, extension proposals, community voting)
  • PR: блоги, конференции, ресурс для поиска работы и людей

Ресурсы

  • http://haxe.org/
  • http://haxe.org/manual/
  • http://haxe.org/blog/
  • http://haxe.org/foundation/people.html
  • http://code.haxe.org/
  • http://try.haxe.org/
  • https://github.com/HaxeFoundation/haxe
  • https://haxe.io/
  • http://groups.google.com/group/haxelang
  • #haxe at freenode IRC

официальные и общепризнанные

Интересные либы

Спасибо за внимание!

вопросы?

Про Haxe

By Dan Korostelev

Про Haxe

  • 5,115