Хабрахабр

[Перевод] Осенний отчет о состоянии Haxe

Линц-ам-Райн (Германия) прошла мини-конференция HaxeUp Sessions 2019, посвященная Haxe и смежным технологиям. 26 октября в г. 0. И самым знаменательным ее событием был, конечно же, финальный релиз Haxe 4. 0. 0 (на момент публикации, то есть спустя примерно неделю, вышло обновление 4. В этом материале я хотел бы представить вам перевод первого доклада конференции — отчета о работе, проделанной командой Haxe за 2019 год. 1).

image

Немного об авторе доклада:

Реализация подобной симуляции требовала постоянных обращений к данным, описывающим состояние частиц (на каждом шаге осуществлялось более 100 запросов к массивам данных о состоянии каждой из ячеек в симуляции), при этом работа с массивами в ActionScript 3 не такая уж и быстрая. Саймон работает с Haxe c 2010 года, когда он был еще студентом и писал работу, посвященную симуляции жидкостей во Flash. В своих поисках Саймон натолкнулся на статью Николя Каннасса (создателя Haxe), посвященную недокументированным тогда опкодам Alchemy, которые не были доступны с помощью ActionScript, но Haxe позволял их использовать. Поэтому первоначальная реализация попросту была нерабочей и требовалось найти решение для данной проблемы. И так, благодаря медленным массивам в ActionScript, Саймон узнал о Haxe. Переписав симуляцию на Haxe с использованием опкодов, Саймон получил рабочую симуляцию!

С 2011 года Саймон присоединился к разработке Haxe, он начал изучать OCaml (на котором написан компилятор) и вносить различные исправления в работу компилятора.

В том же году был создан Haxe Foundation (организация, основным целями которой являются развитие и поддержание экосистемы Haxe, помощь сообществу в организации конференций, консультационные услуги), и Саймон стал одним из его сооснователей. А с 2012 года он стал основным разработчиком компилятора.

image

В 2014-2015 году Саймон пригласил в Haxe Foundation Жозефину Пертозу, которая со временем стала отвечать за организацию конференций и связи с сообществом.

В 2016 году Саймон впервые выступил с докладом о Haxe, а в 2018 организовал первый HaxeUp Sessions.

image

Так что же произошло в мире Haxe за прошедший 2019 год?

0. В феврале и марте вышли 2 релиз-кандидата (4. 0. 0-rc1 и 4. 0-rc2)
В апреле к команде Haxe Foundation присоединились Аурел Били (в качестве интерна) и Александр Кузьменко (в качестве разработчика компилятора).

0. В мае прошел Haxe US Summit 2019.
В июне вышел Haxe 4. А в сентябре — Haxe 4. 0-rc3. 0-rc4 и Haxe 4. 0. 0-rc5. 0.

image

Это значит, что сборка и автоматические тесты теперь выполняются гораздо быстрее.
Хью Сандерсон продолжает работу над hxcpp (библиотекой для поддержки C++ в Haxe).
Внезапно к работе над экстернами для Node.js присоединились пользователи Github terurou и takashiski.
Руди Гес работал над исправлениями и улучшениями для поддержки C# таргета.
Джордж Корни продолжает поддержку генератора HTML-экстернов.
Йенс Фишер работает над vshaxe (расширение для VS Code для работы с Haxe) и над многими другими проектами, связанными с Haxe. Haxe — это не только компилятор, но и целый набор различных инструментов, и в течение года работа над ними также постоянно велась:
Благодаря усилиям Энди Ли Haxe теперь использует Azure Pipelines вместо Travis CI и AppVeyor.

image

0. И главным событием года конечно же стал долгожданный релиз Haxe 4. 3. 0 (а также neko 2. 0), случайно совпавший с HaxeUp 2019 Linz 🙂

image

0. Основную часть доклада Саймон посвятил новым возможностям в Haxe 4. 0 (о них вы также могли узнать из доклада Александра Кузьменко с прошедшего Haxe US Summit 2019).

image

Саймон подробно рассказывал о нем в своем выступлении на Haxe Summit EU 2017. Новый интерпретатор макросов eval в несколько раз быстрее старого. Но с тех пор в нем были улучшены возможности отладки кода, исправлено множество багов, переработана реализация строк.

image

Подробно об этом Саймон рассказывал в своем прошлогоднем выступлении. В Haxe 4 появилась поддержка юникод для всех платформ (кроме Neko). Для конечного пользователя компилятора это означает то, что выражение "Haxeは最高だぞ!".length для всех платформ всегда будет возвращать 10 (опять же, кроме Neko).

Минимально поддерживается кодировка UCS-2 (для каждой платформы / языка используется нативно поддерживаемая кодировка; пытаться поддерживать везде одну и ту же кодировку было бы непрактично):

  • для JavaScript, Flash, HashLink и C++ используется кодировка UCS-2
  • для eval, PHP, lua — UTF-8
  • для Java и C# — UTF-16
  • для Python — UTF-32

Например, если в Java/C#/JavaScript (то есть для строк в UTF-16 и UCS-2 кодировках) запросить длину строки, состоящей из одного эмодзи, то результатом будет “2”. Все символы, находящиеся за пределами основной многоязычной плоскости (в том числе и эмодзи), представляются в виде “суррогатных пар” — такие символы представляются двумя байтами. Этот факт нужно принимать во внимание при работе с такими строками на данных платформах.

В Haxe 4 появился новый вид итератора — “ключ-значение”:

image

Также есть возможность реализовать такой итератор для пользовательских классов, для этого достаточно реализовать для них метод keyValueIterator():KeyValueIterator<K, V>. Он работает с контейнерами типа Map (словари) и строками (с помощью класса StringTools), поддержка для массивов пока что не реализована.

Новый мета-тэг @:using позволяет связывать статические расширения с типами по месту их объявления.

В примере, приведенном на слайде ниже, перечисление MyOption связывается с MyOptionTools, таким образом мы статически расширяем данное перечисление (что в обычной ситуации невозможно) и получаем возможность вызывать метод get(), обращаясь к нему как к методу объекта.

image

В этом примере метод get() является встраиваемым (inline), что также позволяет компилятору дополнительно оптимизировать код: вместо вызова метода MyOptionTools.get(myOption), компилятор подставит хранимое значение, то есть 12.

Для этого при вызове функции нужно дополнительно использовать ключевое слово inline: Если же метод не объявлен как встраиваемый, то еще одним средством оптимизации, доступным программисту, является встраивание функций по месту их вызова (call-site inlining).

image

Все что нужно для этого сделать — просто добавить флаг компиляции -D js-es=6. Благодаря работе Даниила Коростелева в Haxe появилась возможность генерации ES6-классов для JavaScript.

В настоящий момент компилятор генерирует один js-файл для всего проекта (возможно в будущем появится возможность генерации отдельных js-файлов для каждого из классов, но пока что это можно сделать только с помощью дополнительных инструментов).

image

Для абстрактных перечислений теперь автоматически генерируются значения.

В Haxe 4 абстрактные перечисления, созданные поверх Int, ведут себя по тем же правилам, что и в C. В Haxe 3 было необходимо вручную задавать значения для каждого конструктора. Аналогично ведут себя абстрактные перечисления, созданные поверх строк — для них сгенерированные значения будут совпадать с именами конструкторов.

image

Следует упомянуть также некоторые улучшения в синтаксисе:

  • абстрактные перечисления и extern-функции стали полноценными полноценными членами Haxe и для их объявления теперь не нужно использовать мета-тэги @:enum и @:extern
  • в 4-м Haxe используется новый синтаксис для объединения типов (type intersection), лучше отражающий суть расширения структур. Такие конструкции наиболее полезны при объявлении структур данных: выражение typedef T = A & B означает, что структура T имеет все поля, которые есть в типах A и B
  • аналогично в четверке объявляются ограничения типов параметров (type parameters constraints): запись <T:A & B> говорит о том, что тип параметра T должен быть одновременно и A и B.
  • старый синтаксис будет работать (за исключением синтаксиса для ограничений типов, т.к. он будет конфликтовать с новым синтаксисом описания типов функций)

image

Кроме того, новый синтаксис позволяет определять имена аргументов, что можно использовать как часть документации к коду (хотя никак не влияет на саму типизацию). Новый синтаксис для описания типов функций (function type syntax) является более логичным: использование скобок вокруг типов аргументов функции визуально легче воспринимается.

image

в противном случае это потребовало бы слишком много изменений в существующем коде (сам Саймон постоянно ловит себя на том, что по привычке продолжает использовать старый синтаксис). Старый синтаксис при этом продолжает поддерживаться и не объявлен устаревшим, т.к.

В Haxe 4 наконец-то появились стрелочные функции (или лямбда-выражения)!

image

Особенностями стрелочных функций в Haxe являются:

  • неявный return. Если тело функции состоит из одного выражения, то эта функция неявно возвращает значение этого выражения
  • есть возможность задавать типы аргументов функции, т.к. компилятор не всегда может определить требуемый тип (например, Float или Int)
  • если тело функции состоит из нескольких выражений, то потребуется окружить его фигурными скобками
  • но при этом нет возможности явно задать возвращаемый тип функции

В общем, синтаксис стрелочных функций очень похож на используемый в Java 8 (хотя и работает несколько иначе).

Для этого при компиляции проекта под Java достаточно добавить флаг -D jvm. И раз уж мы упомянули Java, то следует сказать о том, что в Haxe 4 появилась возможность генерации JVM-байткода напрямую.

Генерация JVM-байткода означает, что необходимость использования Java-компилятора отпадает, при этом процесс компиляции осуществляется значительно быстрее.

image

Пока что JVM-таргет имеет статус экспериментального по следующим причинам:

  • в некоторых случаях байткод работает несколько медленнее, полученного в результате трансляции Haxe в Java и последующей компиляции с помощью javac. Но команда компилятора в курсе проблемы и знает как ее исправить, просто это требует дополнительной работы.
  • есть проблемы с MethodHandle на Android, что также требует дополнительной работы (Саймон будет рад, если ему помогут в решении этих проблем).

image

Общее сравнение генерации байткода напрямую (genjvm) и компиляции Haxe в Java-код, который затем компилируется в байткод (genjava):

  • как уже упоминалось, по скорости компиляции genjvm работает быстрее genjava
    по скорости исполнения байткода genjvm пока что уступает genjava
  • есть некоторые проблемы при использовании параметров типов (type parameters) и genjava
  • для ссылок на функции в genjvm используются MethodHandle, а в genjava — так называемые “Waneck-функции” (в честь Кауи Ванека, благодаря которому в Haxe появилась поддержка Java и C#). И хотя код, полученный с помощью Waneck-функций не выглядит красиво, он работает и работает достаточно быстро.

Общие советы при работе с Java в Haxe:

  • благодаря тому, что сборщик мусора в Java работает быстро, проблемы связанные с ним редки. Конечно, постоянно создавать новые объекты — не очень хорошая идея, но Java достаточно хорошо справляется с управлением памятью и необходимость постоянно заботиться об аллокациях стоит не так остро, как на некоторых других платформах, поддерживаемых Haxe (например, в HashLink)
  • обращение к полям класса в jvm-таргете может работать очень медленно в случае, когда это делается через структуру (typedef) — пока что компилятор не может оптимизировать такой код
  • следует избегать чрезмерного использования ключевого слова inline — JIT-компилятор неплохо справляется и сам
  • следует избегать использования Null<T>, особенно при работе со сложными математическими вычислениями. Иначе в сгенерированном коде появится множество условных операторов, что крайне негативно скажется на скорости исполнения вашего кода.

Александр Кузьменко подробно о ней рассказал на прошлогоднем HaxeUp. Избежать использования Null<T> может помочь новая фича Haxe 4 — Null-безопасность (Null safety).

image

Для того, чтобы данная функция успешно скомпилировалась, программисту потребуется добавить проверку значения аргумента arg (в противном случае компилятор выдаст сообщение о невозможности вызвать метод charAt() у потенциально нулевого объекта). В примере на слайде выше для статического метода safe() включен Strict-режим проверок на Null-безопасность, при этом данный метод имеет необязательный параметр arg, который может иметь нулевое значение.

image

Null-безопасность можно настраивать как на уровне пакетов (с помощью макроса), так и типов и отдельных полей объектов (с помощью мета-тэга @:nullSafety).

Глобально эти проверки отключены (Off-режим). Режимы, в которых работают проверки на Null-безопасность, следующий: Strict, Loose и Off. Ключевым отличием между Loose и Strict режимами является то, что Loose-режим игнорирует возможность изменения значений между операциями обращений к этим значениям. При их включении по-умолчанию используется Loose-режим (если явно не указать режим). Однако в Strict-режиме данный код не скомпилируется, т.к. В примере на слайде ниже мы видим, что для переменной x добавлена проверка на null. перед непосредственной работой с переменной x вызывается метод sideEffect(), который потенциально может обнулить значение этой переменной, таким образом потребуется добавить еще одну проверку или скопировать значение переменной в локальную переменную, с которой и будем работать в дальнейшем.

image

В Haxe 4 появилось новое ключевое слово final, которое в зависимости от контекста имеет различный смысл:

  • если использовать его вместо ключевого слова var, то объявленному таким образом полю нельзя будет назначить новое значение. Задать его можно будет только напрямую при объявлении (для статических полей) или в конструкторе (для нестатических полей)
  • если использовать его при объявлении класса, то это запретит наследование от него
  • если использовать его в качестве модификатора доступа к свойству объекта, то это запретить переопределение геттера / сеттера в классах-наследниках.

image

Но пока что такая возможность только рассматривается и в компиляторе не реализована. Теоретически компилятор, встретив ключевое слово final, может попытаться оптимизировать код, предполагая, что значение данного поля не меняется.

image

И немного о будущем Haxe:

  • в настоящее время ведется работа над асинхронным API для работы с вводом/выводом
    запланирована поддержка корутин, но пока что работа над ними застряла на этапе планирования. Возможно они появятся в Haxe 4.1, а возможно и позже
  • в компиляторе появится оптимизация хвостовых вызовов (tail-call optimization)
  • и возможно появятся функции, доступные на уровне модуля. Хотя приоритет данной фичи постоянно меняется
Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть