Привет, Хабр! Предлагаю вашему вниманию перевод доклада Александра Кузьменко с прошедшей недавно (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 и о его функциях доступно в его официальном руководстве.

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


  1. PerlPower
    30.06.2019 01:21

    Хороший язык. Вот честно сказать еще лет 10 назад выглядел как сейчас Rust на замену C/C++, только для всякой скриптовщины. На за все 10 лет что я его периодически мониторю, он так и не взлетел. И мне интересно почему.


    1. pecheny
      30.06.2019 08:09
      +2

      Не могу согласиться про скриптовщину, если правильно понял, о чем речь. Haxe никогда не позиционировался ни как «легковстраиваемый рантайм», ни как «shell-ориентированный интерпретатор». Если рассматривать кросс-компиляцию в другой скриптовый язык, то это всегда будет усложнением системы — даже при наличии своих плюсов, не панацея.

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

      Язык мощный, но у него своя ниша. Не всегда там, где он мог бы быть полезен, про него знают/понимают.


  1. L0NGMAN
    30.06.2019 02:32

    В PHP тоже нет автоматическое преобразование типов, если использовать declare(strict_types=1); в начале файла.


    1. khrnsb4y
      30.06.2019 12:49

      Все немного не так. При использовании директивы получим TypeError при несоответствии типов. Без нее как раз будет попытка приведения. Приведение там довольно своеобразное. Можем передавать число вместо строки, int вместо float, или наоборот, но не более.


  1. Taraflex
    30.06.2019 02:40

    во-вторых, можно объединять несколько условий с помощью | (а не дублировать ключевое слово case)

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


    1. Zaphy Автор
      30.06.2019 08:21

      Скорее всего с третьей версии, которая вышла в 2013 году. Так что довольно давно.


    1. ReallyUniqueName
      30.06.2019 09:02

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


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


      1. Taraflex
        30.06.2019 11:52

        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. Zoolander
    30.06.2019 10:55

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

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

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



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

    Правда, я бы советовал смотреть не на количество спрайтов на экране при 60 fps, а на развитость фреймворка, документированность и коммюнити. ah, here we go again )


    1. phantom-code
      30.06.2019 11:41
      +1

      Ben Morris долго пользовался Haxe для геймдева и в итоге создал свой язык — kit.


      1. Zoolander
        30.06.2019 13:36

        посмотрел и в контексте с вышеприведенным BunnyMark повторю — high performance в играх и приложениях является критическим в узком сегменте, да и там часто тормоза возникают не из-за того, что язык или платформа плохо выводит спрайты, а потому что
        — используются живые частицы там, где достаточно спрайтовых анимаций
        — при заходе в дом продолжает рендериться весь игровой мир (еще в Серьезном Сэме мододелы боролись с этим, вручную закрывая двери после захода в комнату)
        — из-за запутанной архитектуры для мелких частных действий вызывается куча перерасчетов в массе объектов, которые вообще никак не должны реагировать
        — разработчик платформы оптимизировал ее под определенные тесты (или писал тесты под платформу), а в вашей игре совсем другие требования и оптимизация разработчика только все усложняет — лучше бы он писал более абстрактно
        — дизайнер игры нарисовал такое, что плохо ложится на платформу в плане скорости

        наибольшая проблема performance — это performance самой разработки )


      1. dmitryhryppa
        30.06.2019 15:56

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

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


        1. Zoolander
          01.07.2019 04:03

          согласен. Пусть расцветают все цветы )


    1. Zaphy Автор
      30.06.2019 12:51

      Я давно мечтаю перевести HaxeFlixel на Kha и получить Khaxel :)


    1. embden
      30.06.2019 16:45
      +1

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


      1. Zoolander
        01.07.2019 04:03

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

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

        PS: говорят, что есть хорошая интеграция с Visual Studio и там есть даже плагин специальный, но я не проверял.


        1. pecheny
          01.07.2019 11:26
          +1

          Вот со студией как раз вообще никакой интеграции нет. У MS есть совершенно отдельный кроссплатформенный редактор VSCode – речь о нем. Для нее MS придумали очень классную штуку – поддержку language server, который позволяет всю интеллектуальную поддержку языка вынести наружу и легко интегрировать. В случае с Haxe так и сделано: сам компилятор, который знает все о типах (даже тех, которые генерируются макросами в процессе компиляции), обеспечивает поддержку языка. Поэтому в VSCode, наравне с другими редакторами, поддерживающими lang серверы, поддержка haxe на уровне.
          FlashDevelop, который сейчас также известен как HaxeDevelop, вроде бы тоже имеет интеграцию компиляторной помощи; а так же много всяких удобных штук для haxe. Он продолжают развиваться.
          Ну и наконец, люди, избалованные JetBrains, могут поставить отличный haxe-плагин, у которого как раз на днях вышел новый релиз. Я пользуюсь именно этим вариантом.


  1. Crysdd
    30.06.2019 13:59

    Забавно. Такой хелловолд из руководства:

    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()
    


    1. Zoolander
      01.07.2019 04:06

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

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

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

      тут надо смотреть на рабочих проектах и вообще надо смотреть — новые функции это хорошо или плохо?


      1. pecheny
        01.07.2019 11:42

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


  1. embden
    01.07.2019 23:49

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


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


    1. pecheny
      02.07.2019 01:08
      +1

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


      1. embden
        02.07.2019 01:35

        Что такое «основная среда», и о какой среде речь?

        Я про Haxedevelop.


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

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


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

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


    1. ReallyUniqueName
      02.07.2019 01:47
      +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/


      1. embden
        02.07.2019 11:04

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


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


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