среда, 2 января 2013 г.

Haxe и трансляция в c++


Сейчас я открываю для себя замечательный язык программирования Haxe и его экосистему, и нахожу эту экосистему чрезвычайно любопытной. 
Одна из интересных вещей - это трансляция в код c++. Которая крайне нуждается в улучшении (http://gamehaxe.com/wp-content/uploads/2012/04/wwx2012-hxcpp.pdf)

А именно, сейчас некоторые категории типов haxe транслируются в тип Dynamic, что есть не очень хорошо в плане производительности: анонимные функции, generic-типы и анонимные типы. 

И, как ни странно, есть несколько решений этих проблем.
1. Прямой путь. Изменение кода транслятора.
Сам транслятор из AST haxe в с++ написан на OCamle. Лучшего языка для обработки AST не найти) Этот код можно найти на http://code.google.com/p/haxe/source/browse/trunk/gencpp.ml. Правда, то, что этот файл состоит из >3000 строк и из-за лаконичной природы OCamlа, читать исходник не очень просто) 

Например, для класса SomeClass<T> неплохо бы генерировать c++ template-класс, и сохранять куда-нибудь в мета-информацию тип T. 

2. Макросы. Т.к. в haxe существует мощная система синтаксических макросов (а-ля Nemerle, Scala 2.10), то такие оптимизационные вещи можно решить через макросы, но с некоторыми ограничениями. Минусы этого подхода:
- Некая синтаксическая зашумленность (см. 2.1). 
- Т.к. классы будут генерироваться на уровне макросов haxe, типы будущих сгенерированных классов нельзя будет явно написать в сигнатуре методов и в определении полей классов. Но, система вывода типов в haxe немного нивелирует этот минус. Хотя полиморфизмом (в терминах ООП) в полной мере нельзя будет пользоваться.

2.1. Generic-классы

Например, вместо
    var c = new SomeClass<Int>(5);
можно написать
    var c = MacroClass.Create(new SomeClass<Int>(5));//преобразуется в var c = new SomeClass_Int(5)
и в этом случае в haxe сгенерируется новый класс SomeClass_Int, который является копией класса SomeClass, где все T заменены на Int. Да, и где-то в самом макросе будет храниться хэш-таблица, где можно будет проверить, сгенерирован ли класс SomeClass со списком типов [Int]. 

Да, тут еще синтаксический минус в том, что в generic-методах generic-типы всегда выводятся, т.е. их нельзя определить явно, как в C#:
    var c = MacroClass.Create<SomeClass<Int>>(5); //так нельзя в haxe, но можно в C#.

Но, благодаря extension-методам в haxe, можно написать так:
    using core.MacroClass;
    ...
    var c = new SomeClass<Int>(5).ToOptimized();

2.1.2. Также в макросе есть некая сложность, связанная с тем, что в нашем generic-классе может быть поле, которое является другим generic-типом. В этом случае наш макрос ToOptimized() должен рекурсивно заменять все встречающиеся упоминания generic-типов на не-generic-типы.

    SomeClass<T> extends BaseClass<T>
    {
        public var Collection : List<T>
    }

В вышеописанном классе необходимо генерировать не-generic-классы BaseClass_SomeType и List_SomeType.

При этом надо полностью возможностями полиморфизма нам не удастся воспользоваться
    var c : BaseClass<Int> = new SomeClass<Int>().ToOptimized(); //не получится
но, можно будет сделать так:

   class AABase<T> { //abstract class, because constructor is none or nonpublic. haxe private = C# protected  
     public function DoSome() { return throw "abstract"; }  
   }  
   class AABase1<T> extends AABase<T> {   
     public function new() { }   
     public override function DoSome() { return "AABase1"; }   
   }  
   class AABase2<T> extends AABase<T> {   
     public function new() { }   
     public override function DoSome() { return "AABase2"; }  
   }  
   
   var arr = [ new AABase1<Int>().ToOptimized(), new AABase2<Float>().ToOptimized() ];  
   for (a in arr) {  
     trace(a.DoSome());  
   }  

В вышеописанном примере arr будет иметь тип Array<BaseClass_Int>, опять же, благодаря системе вывода типов.

2.2. Анонимные функции. 
Из каждой анонимной функции можно генерировать класс со статическим методом (если нет замыканий) или класс с instance-методом (если все же есть замыкания). Но проблема в том, что изменения замыкающих переменных внутри анонимной функции не будут отражаться на внешнем контексте. Т.е. это будет аналогично тому, как в анонимных классах в Java могут использоваться внешние переменные, которые помечены ключевым словом final.