Haxe и PHP: статическая типизация, стрелочные функции, метапрограммирование и многое другое

    Привет, Хабр! Предлагаю вашему вниманию перевод доклада Александра Кузьменко с прошедшей недавно (14-15 июня) конференции Hong Kong Open Source Conference 2019.


    image


    До того, как присоединиться к Haxe Foundation в качестве разработчика компилятора Haxe, Александр около 10 лет профессионально занимался программированием на PHP, так что он знает предмет доклада.


    image


    Небольшое введение о том, что такое Haxe — это кроссплатформенный набор инструментов для создания ПО, в состав которого входят:


    • компилятор, который в зависимости от целевой платформы либо транслирует исходный Haxe-код в исходный код на других языках программирования (C++, PHP, Java, C#, JavaScript, Python, Lua), либо компилирует непосредственно в байт-код виртуальной машины (JVM, neko, HashLink, Flash)
    • стандартная библиотека, реализованная для всех поддерживаемых платформ
    • также Haxe предоставляет средства для взаимодействия с кодом, написанным на языке целевой платформы
    • стандартный менеджер библиотек — haxelib

    image


    Поддержка PHP в Haxe появилась довольно давно — еще в 2008 году. В версиях Haxe до 3.4.7 включительно поддерживался PHP 5.4 и выше, а начиная с четвертой версии в Haxe поддерживается PHP версии 7.0 и выше.


    image


    Вы спросите: зачем PHP-разработчику использовать Haxe?
    Основная причина для этого — возможность использовать одну и ту же логику и на сервере и на клиенте.
    Рассмотрим такой пример: у вас есть сервер, написанный на PHP с использованием фреймворка Laravel, и несколько клиентов, написанных на JavaScript, Java, C#. В таком случае для обработки сетевого взаимодействия между сервером и клиентом (логика которого по идее одна и та же) от вас потребуется написать 4 реализации для каждого из используемых языков. Но с помощью Haxe вы можете написать код сетевого протокола один раз и затем скомпилировать/транслировать его под разные платформы, значительно сэкономив на этом время.


    image


    Вот еще пример — игровое приложение — клон Clash of Clans. Александр участвовал в разработке подобной игры: ее сервер был написан на PHP, мобильный клиент — на C# (Xamarin), а браузерный клиент — на JavaScript с использованием фреймворка Phaser. На клиенте и сервере обрабатывалась одна логика — так называемая "боёвка", которая просчитывала поведение юнитов игроков на локации. Изначально код боёвки был написан для каждой из платформ по-отдельности. Но со временем (а проект развивался около 5 лет) накапливались различия в ее поведении на сервере и на клиентах. Связано это было с тем, что писали ее разные люди, каждый из которых реализовывал ее по-своему. Из-за этого не было надежного способа обнаружения читеров, т.к логика на сервере вела себя совсем не так, как на клиенте, в результате страдали и честные игроки, т.к. сервер мог посчитать их читерами и не засчитать результаты честного боя.
    В конце концов было принято решение перевести боёвку на Haxe, что позволило полностью решить проблему с читерами, т.к. теперь логика боёвки вела себя одинаково на всех платформах. Кроме того, данное решение позволило сократить расходы на дальнейшее развитие боевой системы, т.к. теперь было достаточно одного программиста, знакомого с Haxe, вместо трех, каждый из которых отвечал бы за свою платформу.


    image


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


    image


    А вот так выглядит сгенерированный PHP-код в сравнении с написанным вручную.
    Компилятор Haxe автоматически добавляет в код блок комментариев с описанием типов аргументов функций и возвращаемыми типами, поэтому полученный код можно подключить к PHP-проекту и для него прекрасно будет работать автодополнение.


    Рассмотрим некоторые существенные отличия в синтаксисе Haxe и PHP.


    image


    Начнем с отличий в синтаксисе анонимных функций (на примере их использования для сортировки массива).
    На слайде показана анонимная функция, которая захватывает и изменяет значение локальной переменной desc. В PHP для этого необходимо явно указать, какие переменные доступны в теле анонимной функции. Кроме того, чтобы иметь возможность изменять значение переменной, необходимо добавить & перед ее именем.
    В Haxe такая необходимость отпадает, т.к. компилятор сам определяет, к каким переменным вы обращаетесь. Кроме того, в Haxe 4 появились стрелочные функции (краткая форма описания анонимных функций), используя которые можно сократить наш пример с сортировкой массива всего до одной строки.


    image


    Еще одно отличие в синтаксисе — это различия в описании управляющей конструкции switch. В PHP switch работает так же как и в Си. В Haxe он работает иначе:


    • во-первых, в switch не используется ключевое слово break
    • во-вторых, можно объединять несколько условий с помощью | (а не дублировать ключевое слово case)
    • в-третьих, в Haxe для switch используется pattern matching (механизм сопоставления с образцом). Так, например, можно применить switch к массиву, и в условиях можно определить действия в зависимости от содержимого массива (знак _ означает, что данное значение нас не волнует и может быть любым). Условие [1, _, 3] будет выполнено если массив состоит из трех элементов, при этом первый элемент равен 1, третий — 3, а значение второго — любым.

    image


    В Haxe все является выражением, и это позволяет писать более компактный код. Можно вернуть значение из try/catch, if или switch, не используя ключевое слово return внутри данных конструкций. В приведенном примере с try/catch компилятор "знает", что вы хотите вернуть некоторое значение, и сможет передать его из данной конструкции.


    image


    PHP постепенно движется в направлении более строгой типизации, но в Haxe уже есть строгая статическая типизация!
    В приведенном примере мы присваиваем переменной s значение, полученное из функции functionReturnsString(), которая возвращает строку. Таким образом, тип переменной s — строка. И если попытаться передать ее в функцию giveMeInteger(), ожидающую в качестве аргумента целое число, то компилятор Haxe выдаст ошибку о несоответствии типов.
    Этот пример также демонстрирует еще одну важную особенность Haxe — выведение типов (type inference) — способность компилятора самостоятельно определять типы переменных в зависимости от того, какое значение ей присвоить.


    image


    Для программиста это означает, что в большинстве случаев явно указывать типы переменных необязательно. Так в приведенной функции isSmall() компилятор определит, что тип аргумента a — целое число, т.к. в первой строке тела функции мы сравниваем значение a с целым числом. Далее компилятор на основании того, что во второй строке тела функции мы возвращаем true, определяет, что возвращаемый тип — булева величина. И т.к. компилятор уже определил тип возвращаемого значения, то при дальнейших попытках вернуть из функции какой-либо другой тип, он выдаст ошибку несоответствия типов.


    image


    В Haxe, в отличие от PHP, нет автоматического преобразования типов. Например, в PHP возможно вернуть строку из функции, для которой указано, что она возвращает целое число, в таком случае при выполнении скрипта строка будет преобразована в число (не всегда успешно). А в Haxe аналогичный код попросту не скомпилируется — компилятор выдаст ошибку несоответствия типов.


    image


    Еще одним отличием Haxe является его расширенная система типов, включающая в себя:


    • типы функций, состоящие из типов аргументов функции и возвращаемого типа
    • обобщенные (параметризированные) типы (к ним, например, относятся массивы, для которых в качестве параметра используется тип хранимых значений)
    • перечисляемые типы
    • обобщенные алгебраические типы данных
    • типы анонимных структур позволяют объявлять типы для объектов без объявления классов
    • абстрактные типы данных (абстракции над существующими типами, но без потери производительности в рантайме)

    Все перечисленные типы при компиляции преобразуются в PHP-классы.


    image


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


    image


    В контексте PHP макросы могут например использоваться для роутинга. Скажем, у вас есть простой класс-роутер, в котором реализованы метод для отображения страницы по ее идентификатору, а также метод для выхода из системы. Используя макрос, можно сгенерировать код для метода route(), который на основании http-запроса будет перенаправлять его в соответствующий метод класса Router. Таким образом, отпадает необходимость вручную писать if’ы для вызовов каждого из методов данного класса (макрос сделает это автоматически при компиляции проекта в PHP). Заметьте, что полученный код не использует рефлексию, не требует никаких специальных конфигурационных файлов, или каких-либо еще дополнительных ухищрений, поэтому работать будет он очень быстро.


    image


    Еще один пример использования макросов — генерация кода для парсинга и валидации JSON. К примеру, у вас есть класс Data, объекты которого должны создаваться на основании данных, получаемых из JSON. Это можно сделать с помощью макроса, но так как у макроса есть доступ к структуре класса Data, то в дополнение к парсингу можно сгенерировать код для валидации JSON, добавив выброс исключений при отсутствии полей или несоответствии типа данных. Таким образом, можно быть уверенным в том, что ваше приложение не пропустит некорректные данные, получаемые от пользователей или от стороннего сервера.


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


    image


    В PHP строки бинарно-безопасные (binary safe), поэтому в PHP, если вам не требуется поддержка Юникод, методы для работы со строками работают очень быстро.
    В Haxe, начиная с четвертой версии, строки поддерживают Юникод, поэтому при их компиляции в PHP будут использоваться методы из модуля для работы с многобайтовыми строками mbstring, а это означает медленный доступ к произвольным символам в строке, медленное вычисление длины строки.
    Поэтому если поддержка Юникода вам не нужна, то для работы со строками можно использовать методы класса php.NativeString, которые будут использовать "родные" строки из PHP.


    image


    На слайде слева представлен код на PHP, который использует как методы, поддерживающие Юникод, так и нет.
    Справа представлен эквивалентный код на Haxe. Как видно, если вам нужна поддержка Юникод, то необходимо использовать методы класса String, если нет — то методы класса php.NativeString.


    image


    Еще один важный момент — это работа с массивами.
    В PHP массивы передаются по значению, также массивы поддерживают как числовые, так и строковые ключи.
    В Haxe массивы передаются по ссылке и поддерживают только числовые ключи (если необходимы строковые ключи, то в Haxe для этого следует использовать класс Map). Также в Haxe-массивах не допускаются "дыры“ в индексах (индексы должны идти непрерывно).
    Также стоит отметить, что запись в массив по индексу в Haxe довольно медленная.


    image


    Здесь приведен код для "маппинга" массива с использованием стрелочной функции. Как видно, компилятор Haxe довольно активно оптимизирует PHP-код, получаемый на выходе: анонимная функция в полученном коде отсутствует, вместо этого ее код применяется в цикле к каждому элементу массива. Кроме map() такая оптимизация применяется и к методу filter().
    Также при необходимости в Haxe можно использовать класс php.NativeArray и соответствующие методы PHP для работы с массивами.


    image


    Анонимные объекты реализованы с помощью класса HxAnon, который наследуется от класса StdClass из PHP.


    image


    В PHP StdClass — это класс, в экземпляры которого превращается всё, что мы пытаемся конвертировать в объект. И он бы идеально подошёл для реализации анонимных объектов, если бы не одна особенность их спецификации: в Haxe обращение к несуществующему полю анонимного объекта должно возвращать null, а в PHP это выкидывает warning. Из-за этого пришлось отнаследоваться от стандартного класса из PHP и добавить ему магический метод, который при обращении к несуществующим свойствам возвращает null.


    image


    Haxe может взаимодействовать с кодом, написанным на PHP. Для этого имеются следующие возможности (аналогичные возможностям взаимодействия с JavaScript-кодом):


    • экстерны (externs)
    • вставки PHP-кода напрямую в Haxe-код
    • специальные классы из пакета php.*
      • php.Syntax — для специальных конструкций PHP, которых нет в Haxe
      • php.Global — для "нативных" глобальных функций PHP
      • php.Const — для "нативных" глобальных констант PHP
      • php.SuperGlobal — для сверхглобальных переменных PHP, доступных отовсюду ($_POST, $_GET, $_SERVER и т.п.)

    image


    Т.к. PHP использует классическую ООП-модель, то написание экстернов для него — довольно простой процесс. Фактически экстерны для PHP-классов — это практически дословный "перевод" в Haxe, за исключением некоторых ключевых слов.
    В качестве примера так будет выглядеть код экстерна для PHP-класса со слайда выше:


    image


    Константы из PHP-класса "превращаются" в статические переменные в Haxe-коде (но с добавлением специальных мета-тэгов).
    Статическая переменная $useBuiltinEncoderDecoder становится статической переменной useBuiltinEncoderDecoder.
    Отсюда видно, что экстерны для PHP-классов можно создавать автоматически (Александр планирует реализовать генератор экстернов в этом году).


    image


    Для вставок PHP-кода используется специальный модуль php.Syntax. Код, добавляемый таким способом, не подвергается никаким преобразованиям и оптимизациям со стороны компилятора Haxe.
    Кроме php.Syntax в Haxe осталась возможность использования untyped-кода.
    image


    Также стоит упомянуть такие особенности Haxe, которых нет в PHP:


    • реальные свойства с методами для чтения и записи
    • поля и переменные, доступные только для чтения
    • статические расширения
    • мета-тэги, которые можно использовать для аннотации полей и которые доступны для чтения в макросах. Таким образом, мета-тэги используются в основном для метапрограммирования
    • Null-безопасность (экспериментальная функция)
    • условная компиляция, которая позволяет включать/отключать части кода, которые могут быть доступными, например, в отладочной версии приложения
    • компилятор Haxe генерирует высоко-оптимизированный PHP-код, который может работать в разы быстрее PHP-кода, написанного вручную.

    Больше информации о Haxe и о его функциях доступно в его официальном руководстве.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 25

      0
      Хороший язык. Вот честно сказать еще лет 10 назад выглядел как сейчас Rust на замену C/C++, только для всякой скриптовщины. На за все 10 лет что я его периодически мониторю, он так и не взлетел. И мне интересно почему.
        +4
        Не могу согласиться про скриптовщину, если правильно понял, о чем речь. Haxe никогда не позиционировался ни как «легковстраиваемый рантайм», ни как «shell-ориентированный интерпретатор». Если рассматривать кросс-компиляцию в другой скриптовый язык, то это всегда будет усложнением системы — даже при наличии своих плюсов, не панацея.

        А «не взлетел» он, скорее всего, потому, что нет того, что у HaxeFoundation нет продавана. Самый заметный продаван – Джошуа, но он продвигает OpenFL, из-за чего, к сожалению, Haxe ассоциируется именно с ним.

        Язык мощный, но у него своя ниша. Не всегда там, где он мог бы быть полезен, про него знают/понимают.
        0
        В PHP тоже нет автоматическое преобразование типов, если использовать declare(strict_types=1); в начале файла.
          0
          Все немного не так. При использовании директивы получим TypeError при несоответствии типов. Без нее как раз будет попытка приведения. Приведение там довольно своеобразное. Можем передавать число вместо строки, int вместо float, или наоборот, но не более.
          0
          во-вторых, можно объединять несколько условий с помощью | (а не дублировать ключевое слово case)

          Кошмар какой. Это с какой версии? Давно не в теме. Раньше же использовалась запятая вместо | ( old.haxe.org/ref/syntax?lang=ru#switch ). Что за помутнение заставило Канасье такое запилить?
          Судя по try.haxe.org/#D4A66 даже оборачивание в скобки не помогает.
            0
            Скорее всего с третьей версии, которая вышла в 2013 году. Так что довольно давно.
              0

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


              Что касается вашего примера, то не понятно, чему не помогает оборачивание в скобки?
              Math.random() возвращает Float в пределах от 0 до 1. Поэтому case 1 | 2 никогда не совпадёт с его результатом.

                +1
                Haxe компилятор не анализирует диапазон Math.random() — это так для примера вставил.
                 case ((1 | 2)): trace("1|2");
                преобразуется в
                case 1:case 2:
                		console.log("1|2");
                		break;

                по крайней мере для js таргета, что имхо сильно противоречивое решение. Было бы логично, если бы сгенерировалось
                case 3:
                		console.log("1|2");
                		break;

                для варианта со скобками.
              +1
              я добавлю, со стороны геймдева, что у Haxe есть хорошая игровая либа HaxeFlixel, на которой можно писать игры под веб, десктоп, мобилы. Я писал на ней до 2016, под Android получались очень шустрые и компактные игры. Потом я пересел на Java, на LibGDX по ощущения скорость и требовательность приложений была не лучше или ненамного лучше — простые двухмерные игры шустро шли и на планшетах 2011 года выпуска (что я потерял при переходе на написание исключительно на HTML5 ))

              популярность Haxe сдерживает отсутствие вакансий под него, а отсутствие популярности — сдерживает вакансии. Так-то идея очень крутая — общая кодовая база. Некоторые игры я переписывал три раза на разных платформах, а вот не бегал бы туда-сюда — написал бы в три раза больше )

              update: на волне ностальгии прогуглил — похоже, у HaxeFlixel есть хороший конкурент — мультиплатформенный Kha, который показывает в BunnyMark удивительные рекорды



              (источник скриншота)

              Правда, я бы советовал смотреть не на количество спрайтов на экране при 60 fps, а на развитость фреймворка, документированность и коммюнити. ah, here we go again )
                +1
                Ben Morris долго пользовался Haxe для геймдева и в итоге создал свой язык — kit.
                  0
                  посмотрел и в контексте с вышеприведенным BunnyMark повторю — high performance в играх и приложениях является критическим в узком сегменте, да и там часто тормоза возникают не из-за того, что язык или платформа плохо выводит спрайты, а потому что
                  — используются живые частицы там, где достаточно спрайтовых анимаций
                  — при заходе в дом продолжает рендериться весь игровой мир (еще в Серьезном Сэме мододелы боролись с этим, вручную закрывая двери после захода в комнату)
                  — из-за запутанной архитектуры для мелких частных действий вызывается куча перерасчетов в массе объектов, которые вообще никак не должны реагировать
                  — разработчик платформы оптимизировал ее под определенные тесты (или писал тесты под платформу), а в вашей игре совсем другие требования и оптимизация разработчика только все усложняет — лучше бы он писал более абстрактно
                  — дизайнер игры нарисовал такое, что плохо ложится на платформу в плане скорости

                  наибольшая проблема performance — это performance самой разработки )
                    +1
                    Но он это сделал не потому, что Haxe плохой, а просто потому, что у него свое видение развития языка. Имхо, конечно, но мне кажется, что его язык применим только в ограниченом типе проектов и имеет абсолютно другую философию, т.к. это своего рода очередной D без GC.

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

                    В общем, языки с абсолютно разными целями. Вот :)
                      0
                      согласен. Пусть расцветают все цветы )
                    0
                    Я давно мечтаю перевести HaxeFlixel на Kha и получить Khaxel :)
                      +1

                      А можете описать свой воркфлоу с Haxe? Что использовали, какая интеграция с IDE и есть ли различные анализаторы (статические, динамические)?

                        0
                        workflow был очень прост — FlashDevelop с минимумом подсказок + Android SDK

                        это сейчас я избалован продуктами от JetBrains, а тогда я смело набирал без всяких интеллектуальных подсказок и думал, что все хорошо ))

                        PS: говорят, что есть хорошая интеграция с Visual Studio и там есть даже плагин специальный, но я не проверял.
                          +1
                          Вот со студией как раз вообще никакой интеграции нет. У MS есть совершенно отдельный кроссплатформенный редактор VSCode – речь о нем. Для нее MS придумали очень классную штуку – поддержку language server, который позволяет всю интеллектуальную поддержку языка вынести наружу и легко интегрировать. В случае с Haxe так и сделано: сам компилятор, который знает все о типах (даже тех, которые генерируются макросами в процессе компиляции), обеспечивает поддержку языка. Поэтому в VSCode, наравне с другими редакторами, поддерживающими lang серверы, поддержка haxe на уровне.
                          FlashDevelop, который сейчас также известен как HaxeDevelop, вроде бы тоже имеет интеграцию компиляторной помощи; а так же много всяких удобных штук для haxe. Он продолжают развиваться.
                          Ну и наконец, люди, избалованные JetBrains, могут поставить отличный haxe-плагин, у которого как раз на днях вышел новый релиз. Я пользуюсь именно этим вариантом.
                      +4
                      Забавно. Такой хелловолд из руководства:
                      class Main {
                          static function main() {
                              trace("hello world");
                          }
                      }

                      для Lua генерирует такую портянку:
                      Заголовок спойлера
                      -- Generated by Haxe 4.0.0-rc.3+e3df7a4
                      local _hx_array_mt = {
                        __newindex = function(t,k,v)
                          local len = t.length
                          t.length =  k >= len and (k + 1) or len
                          rawset(t,k,v)
                        end
                      }
                      
                      local function _hx_tab_array(tab,length)
                        tab.length = length
                        return setmetatable(tab, _hx_array_mt)
                      end
                      
                      local function _hx_anon_newindex(t,k,v) t.__fields__[k] = true; rawset(t,k,v); end
                      local _hx_anon_mt = {__newindex=_hx_anon_newindex}
                      local function _hx_a(...)
                        local __fields__ = {};
                        local ret = {__fields__ = __fields__};
                        local max = select('#',...);
                        local tab = {...};
                        local cur = 1;
                        while cur < max do
                          local v = tab[cur];
                          __fields__[v] = true;
                          ret[v] = tab[cur+1];
                          cur = cur + 2
                        end
                        return setmetatable(ret, _hx_anon_mt)
                      end
                      
                      local function _hx_e()
                        return setmetatable({__fields__ = {}}, _hx_anon_mt)
                      end
                      
                      local function _hx_o(obj)
                        return setmetatable(obj, _hx_anon_mt)
                      end
                      
                      local function _hx_new(prototype)
                        return setmetatable({__fields__ = {}}, {__newindex=_hx_anon_newindex, __index=prototype})
                      end
                      
                      local _hxClasses = {}
                      local Int = _hx_e();
                      local Dynamic = _hx_e();
                      local Float = _hx_e();
                      local Bool = _hx_e();
                      local Class = _hx_e();
                      local Enum = _hx_e();
                      
                      local Array = _hx_e()
                      __lua_lib_luautf8_Utf8 = _G.require("lua-utf8")
                      local Main = _hx_e()
                      local Math = _hx_e()
                      local String = _hx_e()
                      local Std = _hx_e()
                      __haxe_Log = _hx_e()
                      __lua_Boot = _hx_e()
                      
                      local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_maxn, _hx_print, _hx_apply_self, _hx_box_mr, _hx_bit_clamp, _hx_table, _hx_bit_raw
                      local _hx_pcall_default = {};
                      local _hx_pcall_break = {};
                      
                      Array.new = function() 
                        local self = _hx_new(Array.prototype)
                        Array.super(self)
                        return self
                      end
                      Array.super = function(self) 
                        _hx_tab_array(self, 0);
                      end
                      Array.prototype = _hx_a();
                      Array.prototype.concat = function(self,a) 
                        local _g = _hx_tab_array({}, 0);
                        local _g1 = 0;
                        local _g2 = self;
                        while (_g1 < _g2.length) do 
                          local i = _g2[_g1];
                          _g1 = _g1 + 1;
                          _g:push(i);
                        end;
                        local ret = _g;
                        local _g3 = 0;
                        while (_g3 < a.length) do 
                          local i1 = a[_g3];
                          _g3 = _g3 + 1;
                          ret:push(i1);
                        end;
                        do return ret end
                      end
                      Array.prototype.join = function(self,sep) 
                        local tbl = ({});
                        local _gthis = self;
                        local cur_length = 0;
                        local i = _hx_o({__fields__={hasNext=true,next=true},hasNext=function(self) 
                          do return cur_length < _gthis.length end;
                        end,next=function(self) 
                          cur_length = cur_length + 1;
                          do return _gthis[cur_length - 1] end;
                        end});
                        while (i:hasNext()) do 
                          local i1 = i:next();
                          _G.table.insert(tbl, Std.string(i1));
                        end;
                        do return _G.table.concat(tbl, sep) end
                      end
                      Array.prototype.pop = function(self) 
                        if (self.length == 0) then 
                          do return nil end;
                        end;
                        local ret = self[self.length - 1];
                        self[self.length - 1] = nil;
                        self.length = self.length - 1;
                        do return ret end
                      end
                      Array.prototype.push = function(self,x) 
                        self[self.length] = x;
                        do return self.length end
                      end
                      Array.prototype.reverse = function(self) 
                        local tmp;
                        local i = 0;
                        while (i < Std.int(self.length / 2)) do 
                          tmp = self[i];
                          self[i] = self[(self.length - i) - 1];
                          self[(self.length - i) - 1] = tmp;
                          i = i + 1;
                        end;
                      end
                      Array.prototype.shift = function(self) 
                        if (self.length == 0) then 
                          do return nil end;
                        end;
                        local ret = self[0];
                        if (self.length == 1) then 
                          self[0] = nil;
                        else
                          if (self.length > 1) then 
                            self[0] = self[1];
                            _G.table.remove(self, 1);
                          end;
                        end;
                        local tmp = self;
                        tmp.length = tmp.length - 1;
                        do return ret end
                      end
                      Array.prototype.slice = function(self,pos,_end) 
                        if ((_end == nil) or (_end > self.length)) then 
                          _end = self.length;
                        else
                          if (_end < 0) then 
                            _end = _G.math.fmod((self.length - (_G.math.fmod(-_end, self.length))), self.length);
                          end;
                        end;
                        if (pos < 0) then 
                          pos = _G.math.fmod((self.length - (_G.math.fmod(-pos, self.length))), self.length);
                        end;
                        if ((pos > _end) or (pos > self.length)) then 
                          do return _hx_tab_array({}, 0) end;
                        end;
                        local ret = _hx_tab_array({}, 0);
                        local _g = pos;
                        local _g1 = _end;
                        while (_g < _g1) do 
                          _g = _g + 1;
                          local i = _g - 1;
                          ret:push(self[i]);
                        end;
                        do return ret end
                      end
                      Array.prototype.sort = function(self,f) 
                        local i = 0;
                        local l = self.length;
                        while (i < l) do 
                          local swap = false;
                          local j = 0;
                          local max = (l - i) - 1;
                          while (j < max) do 
                            if (f(self[j], self[j + 1]) > 0) then 
                              local tmp = self[j + 1];
                              self[j + 1] = self[j];
                              self[j] = tmp;
                              swap = true;
                            end;
                            j = j + 1;
                          end;
                          if (not swap) then 
                            break;
                          end;
                          i = i + 1;
                        end;
                      end
                      Array.prototype.splice = function(self,pos,len) 
                        if ((len < 0) or (pos > self.length)) then 
                          do return _hx_tab_array({}, 0) end;
                        else
                          if (pos < 0) then 
                            pos = self.length - (_G.math.fmod(-pos, self.length));
                          end;
                        end;
                        len = Math.min(len, self.length - pos);
                        local ret = _hx_tab_array({}, 0);
                        local _g = pos;
                        local _g1 = pos + len;
                        while (_g < _g1) do 
                          _g = _g + 1;
                          local i = _g - 1;
                          ret:push(self[i]);
                          self[i] = self[i + len];
                        end;
                        local _g2 = pos + len;
                        local _g3 = self.length;
                        while (_g2 < _g3) do 
                          _g2 = _g2 + 1;
                          local i1 = _g2 - 1;
                          self[i1] = self[i1 + len];
                        end;
                        local tmp = self;
                        tmp.length = tmp.length - len;
                        do return ret end
                      end
                      Array.prototype.toString = function(self) 
                        local tbl = ({});
                        _G.table.insert(tbl, "[");
                        _G.table.insert(tbl, self:join(","));
                        _G.table.insert(tbl, "]");
                        do return _G.table.concat(tbl, "") end
                      end
                      Array.prototype.unshift = function(self,x) 
                        local len = self.length;
                        local _g = 0;
                        local _g1 = len;
                        while (_g < _g1) do 
                          _g = _g + 1;
                          local i = _g - 1;
                          self[len - i] = self[(len - i) - 1];
                        end;
                        self[0] = x;
                      end
                      Array.prototype.insert = function(self,pos,x) 
                        if (pos > self.length) then 
                          pos = self.length;
                        end;
                        if (pos < 0) then 
                          pos = self.length + pos;
                          if (pos < 0) then 
                            pos = 0;
                          end;
                        end;
                        local cur_len = self.length;
                        while (cur_len > pos) do 
                          self[cur_len] = self[cur_len - 1];
                          cur_len = cur_len - 1;
                        end;
                        self[pos] = x;
                      end
                      Array.prototype.remove = function(self,x) 
                        local _g = 0;
                        local _g1 = self.length;
                        while (_g < _g1) do 
                          _g = _g + 1;
                          local i = _g - 1;
                          if (self[i] == x) then 
                            local _g2 = i;
                            local _g11 = self.length - 1;
                            while (_g2 < _g11) do 
                              _g2 = _g2 + 1;
                              local j = _g2 - 1;
                              self[j] = self[j + 1];
                            end;
                            self[self.length - 1] = nil;
                            self.length = self.length - 1;
                            do return true end;
                          end;
                        end;
                        do return false end
                      end
                      Array.prototype.indexOf = function(self,x,fromIndex) 
                        local _end = self.length;
                        if (fromIndex == nil) then 
                          fromIndex = 0;
                        else
                          if (fromIndex < 0) then 
                            fromIndex = self.length + fromIndex;
                            if (fromIndex < 0) then 
                              fromIndex = 0;
                            end;
                          end;
                        end;
                        local _g = fromIndex;
                        local _g1 = _end;
                        while (_g < _g1) do 
                          _g = _g + 1;
                          local i = _g - 1;
                          if (x == self[i]) then 
                            do return i end;
                          end;
                        end;
                        do return -1 end
                      end
                      Array.prototype.lastIndexOf = function(self,x,fromIndex) 
                        if ((fromIndex == nil) or (fromIndex >= self.length)) then 
                          fromIndex = self.length - 1;
                        else
                          if (fromIndex < 0) then 
                            fromIndex = self.length + fromIndex;
                            if (fromIndex < 0) then 
                              do return -1 end;
                            end;
                          end;
                        end;
                        local i = fromIndex;
                        while (i >= 0) do 
                          if (self[i] == x) then 
                            do return i end;
                          else
                            i = i - 1;
                          end;
                        end;
                        do return -1 end
                      end
                      Array.prototype.copy = function(self) 
                        local _g = _hx_tab_array({}, 0);
                        local _g1 = 0;
                        local _g2 = self;
                        while (_g1 < _g2.length) do 
                          local i = _g2[_g1];
                          _g1 = _g1 + 1;
                          _g:push(i);
                        end;
                        do return _g end
                      end
                      Array.prototype.map = function(self,f) 
                        local _g = _hx_tab_array({}, 0);
                        local _g1 = 0;
                        local _g2 = self;
                        while (_g1 < _g2.length) do 
                          local i = _g2[_g1];
                          _g1 = _g1 + 1;
                          _g:push(f(i));
                        end;
                        do return _g end
                      end
                      Array.prototype.filter = function(self,f) 
                        local _g = _hx_tab_array({}, 0);
                        local _g1 = 0;
                        local _g2 = self;
                        while (_g1 < _g2.length) do 
                          local i = _g2[_g1];
                          _g1 = _g1 + 1;
                          if (f(i)) then 
                            _g:push(i);
                          end;
                        end;
                        do return _g end
                      end
                      Array.prototype.iterator = function(self) 
                        local _gthis = self;
                        local cur_length = 0;
                        do return _hx_o({__fields__={hasNext=true,next=true},hasNext=function(self) 
                          do return cur_length < _gthis.length end;
                        end,next=function(self) 
                          cur_length = cur_length + 1;
                          do return _gthis[cur_length - 1] end;
                        end}) end
                      end
                      Array.prototype.resize = function(self,len) 
                        if (self.length < len) then 
                          self.length = len;
                        else
                          if (self.length > len) then 
                            local _g = len;
                            local _g1 = self.length;
                            while (_g < _g1) do 
                              _g = _g + 1;
                              local i = _g - 1;
                              self[i] = nil;
                            end;
                            self.length = len;
                          end;
                        end;
                      end
                      
                      Main.new = {}
                      Main.main = function() 
                        __haxe_Log.trace("hello world", _hx_o({__fields__={fileName=true,lineNumber=true,className=true,methodName=true},fileName="Main.hx",lineNumber=3,className="Main",methodName="main"}));
                      end
                      
                      Math.new = {}
                      Math.isNaN = function(f) 
                        do return f ~= f end;
                      end
                      Math.isFinite = function(f) 
                        if (f > -_G.math.huge) then 
                          do return f < _G.math.huge end;
                        else
                          do return false end;
                        end;
                      end
                      Math.min = function(a,b) 
                        if (Math.isNaN(a) or Math.isNaN(b)) then 
                          do return (0/0) end;
                        else
                          do return _G.math.min(a, b) end;
                        end;
                      end
                      
                      String.new = function(string) 
                        local self = _hx_new(String.prototype)
                        String.super(self,string)
                        self = string
                        return self
                      end
                      String.super = function(self,string) 
                      end
                      String.__index = function(s,k) 
                        if (k == "length") then 
                          do return __lua_lib_luautf8_Utf8.len(s) end;
                        else
                          local o = String.prototype;
                          local field = k;
                          if ((function() 
                            local _hx_1
                            if ((_G.type(o) == "string") and ((String.prototype[field] ~= nil) or (field == "length"))) then 
                            _hx_1 = true; elseif (o.__fields__ ~= nil) then 
                            _hx_1 = o.__fields__[field] ~= nil; else 
                            _hx_1 = o[field] ~= nil; end
                            return _hx_1
                          end )()) then 
                            do return String.prototype[k] end;
                          else
                            if (String.__oldindex ~= nil) then 
                              if (_G.type(String.__oldindex) == "function") then 
                                do return String.__oldindex(s, k) end;
                              else
                                if (_G.type(String.__oldindex) == "table") then 
                                  do return String.__oldindex[k] end;
                                end;
                              end;
                              do return nil end;
                            else
                              do return nil end;
                            end;
                          end;
                        end;
                      end
                      String.fromCharCode = function(code) 
                        do return __lua_lib_luautf8_Utf8.char(code) end;
                      end
                      String.prototype = _hx_a();
                      String.prototype.toUpperCase = function(self) 
                        do return __lua_lib_luautf8_Utf8.upper(self) end
                      end
                      String.prototype.toLowerCase = function(self) 
                        do return __lua_lib_luautf8_Utf8.lower(self) end
                      end
                      String.prototype.indexOf = function(self,str,startIndex) 
                        if (startIndex == nil) then 
                          startIndex = 1;
                        else
                          startIndex = startIndex + 1;
                        end;
                        local r = __lua_lib_luautf8_Utf8.find(self, str, startIndex, true);
                        if ((r ~= nil) and (r > 0)) then 
                          do return r - 1 end;
                        else
                          do return -1 end;
                        end;
                      end
                      String.prototype.lastIndexOf = function(self,str,startIndex) 
                        local i = 0;
                        local ret = -1;
                        if (startIndex == nil) then 
                          startIndex = __lua_lib_luautf8_Utf8.len(self);
                        end;
                        while (true) do 
                          local startIndex1 = ret + 1;
                          if (startIndex1 == nil) then 
                            startIndex1 = 1;
                          else
                            startIndex1 = startIndex1 + 1;
                          end;
                          local r = __lua_lib_luautf8_Utf8.find(self, str, startIndex1, true);
                          local p = (function() 
                            local _hx_1
                            if ((r ~= nil) and (r > 0)) then 
                            _hx_1 = r - 1; else 
                            _hx_1 = -1; end
                            return _hx_1
                          end )();
                          if ((p == -1) or (p > startIndex)) then 
                            break;
                          end;
                          ret = p;
                        end;
                        do return ret end
                      end
                      String.prototype.split = function(self,delimiter) 
                        local idx = 1;
                        local ret = _hx_tab_array({}, 0);
                        local delim_offset = (function() 
                          local _hx_1
                          if (__lua_lib_luautf8_Utf8.len(delimiter) > 0) then 
                          _hx_1 = __lua_lib_luautf8_Utf8.len(delimiter); else 
                          _hx_1 = 1; end
                          return _hx_1
                        end )();
                        while (idx ~= nil) do 
                          local newidx = 0;
                          if (__lua_lib_luautf8_Utf8.len(delimiter) > 0) then 
                            newidx = __lua_lib_luautf8_Utf8.find(self, delimiter, idx, true);
                          else
                            if (idx >= __lua_lib_luautf8_Utf8.len(self)) then 
                              newidx = nil;
                            else
                              newidx = idx + 1;
                            end;
                          end;
                          if (newidx ~= nil) then 
                            local match = __lua_lib_luautf8_Utf8.sub(self, idx, newidx - 1);
                            ret:push(match);
                            idx = newidx + __lua_lib_luautf8_Utf8.len(delimiter);
                          else
                            ret:push(__lua_lib_luautf8_Utf8.sub(self, idx, __lua_lib_luautf8_Utf8.len(self)));
                            idx = nil;
                          end;
                        end;
                        do return ret end
                      end
                      String.prototype.toString = function(self) 
                        do return self end
                      end
                      String.prototype.substring = function(self,startIndex,endIndex) 
                        if (endIndex == nil) then 
                          endIndex = __lua_lib_luautf8_Utf8.len(self);
                        end;
                        if (endIndex < 0) then 
                          endIndex = 0;
                        end;
                        if (startIndex < 0) then 
                          startIndex = 0;
                        end;
                        if (endIndex < startIndex) then 
                          do return __lua_lib_luautf8_Utf8.sub(self, endIndex + 1, startIndex) end;
                        else
                          do return __lua_lib_luautf8_Utf8.sub(self, startIndex + 1, endIndex) end;
                        end;
                      end
                      String.prototype.charAt = function(self,index) 
                        do return __lua_lib_luautf8_Utf8.sub(self, index + 1, index + 1) end
                      end
                      String.prototype.charCodeAt = function(self,index) 
                        do return __lua_lib_luautf8_Utf8.byte(self, index + 1) end
                      end
                      String.prototype.substr = function(self,pos,len) 
                        if ((len == nil) or (len > (pos + __lua_lib_luautf8_Utf8.len(self)))) then 
                          len = __lua_lib_luautf8_Utf8.len(self);
                        else
                          if (len < 0) then 
                            len = __lua_lib_luautf8_Utf8.len(self) + len;
                          end;
                        end;
                        if (pos < 0) then 
                          pos = __lua_lib_luautf8_Utf8.len(self) + pos;
                        end;
                        if (pos < 0) then 
                          pos = 0;
                        end;
                        do return __lua_lib_luautf8_Utf8.sub(self, pos + 1, pos + len) end
                      end
                      
                      Std.new = {}
                      Std.string = function(s) 
                        do return __lua_Boot.__string_rec(s) end;
                      end
                      Std.int = function(x) 
                        if (not Math.isFinite(x) or Math.isNaN(x)) then 
                          do return 0 end;
                        else
                          do return _hx_bit_clamp(x) end;
                        end;
                      end
                      
                      __haxe_Log.new = {}
                      __haxe_Log.formatOutput = function(v,infos) 
                        local str = Std.string(v);
                        if (infos == nil) then 
                          do return str end;
                        end;
                        local pstr = Std.string(Std.string(infos.fileName) .. Std.string(":")) .. Std.string(infos.lineNumber);
                        if (infos.customParams ~= nil) then 
                          local _g = 0;
                          local _g1 = infos.customParams;
                          while (_g < _g1.length) do 
                            local v1 = _g1[_g];
                            _g = _g + 1;
                            str = Std.string(str) .. Std.string((Std.string(", ") .. Std.string(Std.string(v1))));
                          end;
                        end;
                        do return Std.string(Std.string(pstr) .. Std.string(": ")) .. Std.string(str) end;
                      end
                      __haxe_Log.trace = function(v,infos) 
                        local str = __haxe_Log.formatOutput(v, infos);
                        _hx_print(str);
                      end
                      
                      __lua_Boot.new = {}
                      __lua_Boot.isArray = function(o) 
                        if (_G.type(o) == "table") then 
                          if ((o.__enum__ == nil) and (_G.getmetatable(o) ~= nil)) then 
                            do return _G.getmetatable(o).__index == Array.prototype end;
                          else
                            do return false end;
                          end;
                        else
                          do return false end;
                        end;
                      end
                      __lua_Boot.printEnum = function(o,s) 
                        if (o.length == 2) then 
                          do return o[0] end;
                        else
                          local str = Std.string(Std.string(o[0])) .. Std.string("(");
                          s = Std.string(s) .. Std.string("\t");
                          local _g = 2;
                          local _g1 = o.length;
                          while (_g < _g1) do 
                            _g = _g + 1;
                            local i = _g - 1;
                            if (i ~= 2) then 
                              str = Std.string(str) .. Std.string((Std.string(",") .. Std.string(__lua_Boot.__string_rec(o[i], s))));
                            else
                              str = Std.string(str) .. Std.string(__lua_Boot.__string_rec(o[i], s));
                            end;
                          end;
                          do return Std.string(str) .. Std.string(")") end;
                        end;
                      end
                      __lua_Boot.printClassRec = function(c,result,s) 
                        if (result == nil) then 
                          result = "";
                        end;
                        local f = __lua_Boot.__string_rec;
                        for k,v in pairs(c) do if result ~= '' then result = result .. ', ' end result = result .. k .. ':' .. f(v, s.. '	') end;
                        do return result end;
                      end
                      __lua_Boot.__string_rec = function(o,s) 
                        if (s == nil) then 
                          s = "";
                        end;
                        if (__lua_lib_luautf8_Utf8.len(s) >= 5) then 
                          do return "<...>" end;
                        end;
                        local _g = type(o);
                        if (_g) == "boolean" then 
                          do return tostring(o) end;
                        elseif (_g) == "function" then 
                          do return "<function>" end;
                        elseif (_g) == "nil" then 
                          do return "null" end;
                        elseif (_g) == "number" then 
                          if (o == _G.math.huge) then 
                            do return "Infinity" end;
                          else
                            if (o == -_G.math.huge) then 
                              do return "-Infinity" end;
                            else
                              if (o == 0) then 
                                do return "0" end;
                              else
                                if (o ~= o) then 
                                  do return "NaN" end;
                                else
                                  do return tostring(o) end;
                                end;
                              end;
                            end;
                          end;
                        elseif (_g) == "string" then 
                          do return o end;
                        elseif (_g) == "table" then 
                          if (o.__enum__ ~= nil) then 
                            do return __lua_Boot.printEnum(o, s) end;
                          else
                            if ((_hx_wrap_if_string_field(o,'toString') ~= nil) and not __lua_Boot.isArray(o)) then 
                              do return _hx_wrap_if_string_field(o,'toString')(o) end;
                            else
                              if (__lua_Boot.isArray(o)) then 
                                local o2 = o;
                                if (__lua_lib_luautf8_Utf8.len(s) > 5) then 
                                  do return "[...]" end;
                                else
                                  local _g1 = _hx_tab_array({}, 0);
                                  local _g11 = 0;
                                  while (_g11 < o2.length) do 
                                    local i = o2[_g11];
                                    _g11 = _g11 + 1;
                                    _g1:push(__lua_Boot.__string_rec(i, Std.string(s) .. Std.string(1)));
                                  end;
                                  do return Std.string(Std.string("[") .. Std.string(_g1:join(","))) .. Std.string("]") end;
                                end;
                              else
                                if (o.__class__ ~= nil) then 
                                  do return Std.string(Std.string("{") .. Std.string(__lua_Boot.printClassRec(o, "", Std.string(s) .. Std.string("\t")))) .. Std.string("}") end;
                                else
                                  local fields = __lua_Boot.fieldIterator(o);
                                  local buffer = ({});
                                  local first = true;
                                  _G.table.insert(buffer, "{ ");
                                  local f = fields;
                                  while (f:hasNext()) do 
                                    local f1 = f:next();
                                    if (first) then 
                                      first = false;
                                    else
                                      _G.table.insert(buffer, ", ");
                                    end;
                                    _G.table.insert(buffer, Std.string(Std.string(Std.string("") .. Std.string(Std.string(f1))) .. Std.string(" : ")) .. Std.string(__lua_Boot.__string_rec(o[f1], Std.string(s) .. Std.string("\t"))));
                                  end;
                                  _G.table.insert(buffer, " }");
                                  do return _G.table.concat(buffer, "") end;
                                end;
                              end;
                            end;
                          end;
                        elseif (_g) == "thread" then 
                          do return "<thread>" end;
                        elseif (_g) == "userdata" then 
                          local mt = _G.getmetatable(o);
                          if ((mt ~= nil) and (mt.__tostring ~= nil)) then 
                            do return _G.tostring(o) end;
                          else
                            do return "<userdata>" end;
                          end;else
                        _G.error("Unknown Lua type",0); end;
                      end
                      __lua_Boot.fieldIterator = function(o) 
                        if (_G.type(o) ~= "table") then 
                          do return _hx_o({__fields__={next=true,hasNext=true},next=function(self) 
                            do return nil end;
                          end,hasNext=function(self) 
                            do return false end;
                          end}) end;
                        end;
                        local tbl = (function() 
                          local _hx_1
                          if (o.__fields__ ~= nil) then 
                          _hx_1 = o.__fields__; else 
                          _hx_1 = o; end
                          return _hx_1
                        end )();
                        local cur = _G.pairs(tbl);
                        local next_valid = function(tbl1,val) 
                          while (__lua_Boot.hiddenFields[val] ~= nil) do 
                            val = cur(tbl1, val);
                          end;
                          do return val end;
                        end;
                        local cur_val = next_valid(tbl, cur(tbl, nil));
                        do return _hx_o({__fields__={next=true,hasNext=true},next=function(self) 
                          local ret = cur_val;
                          cur_val = next_valid(tbl, cur(tbl, cur_val));
                          do return ret end;
                        end,hasNext=function(self) 
                          do return cur_val ~= nil end;
                        end}) end;
                      end
                      _hx_bit_clamp = function(v)
                        if v <= 2147483647 and v >= -2147483648 then
                          if v > 0 then return _G.math.floor(v)
                          else return _G.math.ceil(v)
                          end
                        end
                        if v > 2251798999999999 then v = v*2 end;
                        if (v ~= v or math.abs(v) == _G.math.huge) then return nil end
                        return _hx_bit.band(v, 2147483647 ) - math.abs(_hx_bit.band(v, 2147483648))
                      end
                      
                      -- require this for lua 5.1
                      pcall(require, 'bit')
                      if bit then
                        _hx_bit = bit
                      else
                        local _hx_bit_raw = _G.require('bit32')
                        _hx_bit = setmetatable({}, { __index = _hx_bit_raw });
                        -- lua 5.2 weirdness
                        _hx_bit.bnot = function(...) return _hx_bit_clamp(_hx_bit_raw.bnot(...)) end;
                        _hx_bit.bxor = function(...) return _hx_bit_clamp(_hx_bit_raw.bxor(...)) end;
                      end
                      
                      _hx_array_mt.__index = Array.prototype
                      
                      local _hx_static_init = function()
                        __lua_Boot.hiddenFields = {__id__=true, hx__closures=true, super=true, prototype=true, __fields__=true, __ifields__=true, __class__=true, __properties__=true}
                        
                      end
                      
                      _hx_print = print or (function() end)
                      
                      _hx_wrap_if_string_field = function(o, fld)
                        if _G.type(o) == 'string' then
                          if fld == 'length' then
                            return _G.string.len(o)
                          else
                            return String.prototype[fld]
                          end
                        else
                          return o[fld]
                        end
                      end
                      
                      _hx_static_init();
                      Main.main()
                      

                        0
                        похоже на реализацию некоторых функций, которых нет (или якобы нет) в Lua

                        в JavaScript похожая штука с полифиллами и библиотеками — но Hello World там пишется намного чище

                        пожалуй, наиболее близкая аналогия — Kotlin, еще один траспайлерный язык, который при переводе в JavaScript добавляет еще свой рантайм — несмертельно большой, но видимый

                        тут надо смотреть на рабочих проектах и вообще надо смотреть — новые функции это хорошо или плохо?
                          0
                          Да, часто языки, имеющие таргетами другие языки, тащат за собой рантайм и std-либу, которые могут весить очень много. Когда-то давно смотрел, что scala-js выдавала helloworld на несколько мегов.
                          В случае Haxe, ему приходится обеспечивать одинаковую работу строк, массивов, трейсов и прочего на всем заопарке целевых платформ (а заопарк впечатляющий).
                          Поэтому рантайм в пару десятков кб – довольно скромно. Это не множитель к размеру, а слагаемое. На helloworld-ах его вклад, конечно, наиболее заметен.
                          У Haxe в отличие от многих транспайлеров есть отличный dce, который вычищает почти весь неиспользуемый код, поэтому в результат попадает лишь необходимый минимум стандартной библиотеки.
                        0

                        Вот почему у меня Haxe не начался. Во-первых, почему-то основная среда разработки кросс-платформенного языка Windows-only. Во-вторых, на Linux нельзя поставить решение "из коробки" из репозитория. Захотел я попробовать Haxe, а из репозиториев можно только вим с плагином поставить. В-третьих, при заходе на Discord для получения помощи надо пройти регистрацию. К тому же сам дискорд не всем нравится (почему бы не сделать транспорт из IRC или matrix). В-четвертых, почему-то качество туториалов довольно низкое. Вот, к примеру, введение.


                        Вроде бы это всё и мелочи, а желание разбираться с Haxe и разработкой игр не нём пропало.

                          +1
                          Что такое «основная среда», и о какой среде речь? Выше в ветке были названы три распространенных варианта, два из которых кроссплатформенные. Этак и до плюсов докапаться можно из-за студии. Какое вообще отношение язык имеет к дистрибьюции VSCode или Intellij IDEA?
                          Документация по самому языку довольно неплохо написано, много хороших и интересных примеров можно найти в разделе cookbook.
                          Что до стартовых туториалов, особенно в контексте разработки игр – с haxe слишком много вариантов того, как начать, и в какую сторону двигаться. Гида по этим вариантам, действиетльно, не хватает. А сами туториалы есть не только у языка, но и у игровых движков; для старта может оказаться полезным смотреть на них. Каналов для общения хватает: есть и официальны форум, гитхаб, сообщества игровых движков. В русскоязычном сегменте есть скайп и телеграм-чаты.
                          Чтобы было более понятна ситуация приведу пример с шарпом, на котором тоже можно делать игры. Делать можно на юнити или на моногейм, а Introduction to the C#с MSDN не слишком поможет стартануть.
                            0
                            Что такое «основная среда», и о какой среде речь?

                            Я про Haxedevelop.


                            Этак и до плюсов докапаться можно из-за студии. Какое вообще отношение язык имеет к дистрибьюции VSCode или Intellij IDEA?

                            Самое прямое — чем легче начать использовать язык или фреймворк, тем больше вероятность его распространения. Чем легче и интереснее туториалы, тем выше вероятность того, что языком человек заинтересуется. Взять, к примеру, godot-engine. В его туториалах с первых же шагов делают игру! Чтобы начать им пользоваться, надо просто скачать и распаковать бинарник. Я сам с удовольствием прошёл этот туториал. А в Haxe сначала надо выбрать среду разработки (я не хочу устанавливать на личном компьютере софт не из репозиториев), потом выбрать движок, потом учиться на мануалах, а не на туториалах. И вот даже выбрал я HaxeFlixel, открываю туториал, а там говорится, что надо установить FlashDevelop, а он Windows-only.


                            Что до стартовых туториалов, особенно в контексте разработки игр – с haxe слишком много вариантов того, как начать, и в какую сторону двигаться. Гида по этим вариантам, действиетльно, не хватает.

                            Вот мне не удалось даже начать, при этом с Godot начать удалось, с libsdl тоже, а вот с Haxe оказалось сложнее.

                            +1

                            Для различных дистрибутивов Linux есть пакеты в официальных репозиториях дистрибутивов: https://haxe.org/download/linux/ — Ubuntu, Debian, CentOS, OpenSUSE, Arch. Даже для Fedora есть.


                            В качестве основной IDE для Haxe сейчас использую VSCode, и хаксовый плагин там в отличном состоянии. Работает на Windows, Linux, OSX.


                            В документации действительно есть пробелы, хотя в последнее время ситуация постепенно улучшается.
                            Но раз уж вы упомянули, что хотели в геймдев, то в таком случае стоит скорее обращать внимание на документацию фреймворков.
                            Вот, к примеру, Heaps (который в Dead Cells используется): https://heaps.io/documentation/home.html
                            Вот OpenFL (использовался в Papers Please): https://books.openfl.org/openfl-developers-guide/
                            HaxeFlixel: https://haxeflixel.com/documentation/
                            Вполне подробные доки.


                            Что касается Discord, то это уже вопрос вкуса. К любым каналам общения у кого-нибудь находятся претензии. Тут, к сожалению, всем не угодишь. Сейчас ещё появился Discourse: https://community.haxe.org/

                              0

                              Так а в чем Вы меня хотите переубедить? Я захотел попробовать, у меня это сделать быстро и легко не получилось. Устанавливать программы не из репозитория, плагины к ним, искать хорошие туториалы (вместо тех мануалов, что на сайте) — не у каждого хватит мотивации.


                              Взять тот же heaps, смотрим его туториал Hello World. Сначала создать xml-конфиг, добавить текст программы, потом создать build task в vscode (что делать пользователям других IDE?) — уже на этом этапе часть пользователей подумает, что фреймворк излишне запутанный (не сложный). В нормальных случаях для того, чтобы запустить Hello World, надо выбрать соответствующий пример из базы примеров и нажать 'Run'.


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

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое