Привет, Хабр! Предлагаю вашему вниманию перевод доклада Александра Кузьменко с прошедшей недавно (14-15 июня) конференции Hong Kong Open Source Conference 2019.
До того, как присоединиться к Haxe Foundation в качестве разработчика компилятора Haxe, Александр около 10 лет профессионально занимался программированием на PHP, так что он знает предмет доклада.
Небольшое введение о том, что такое Haxe — это кроссплатформенный набор инструментов для создания ПО, в состав которого входят:
- компилятор, который в зависимости от целевой платформы либо транслирует исходный Haxe-код в исходный код на других языках программирования (C++, PHP, Java, C#, JavaScript, Python, Lua), либо компилирует непосредственно в байт-код виртуальной машины (JVM, neko, HashLink, Flash)
- стандартная библиотека, реализованная для всех поддерживаемых платформ
- также Haxe предоставляет средства для взаимодействия с кодом, написанным на языке целевой платформы
- стандартный менеджер библиотек — haxelib
Поддержка PHP в Haxe появилась довольно давно — еще в 2008 году. В версиях Haxe до 3.4.7 включительно поддерживался PHP 5.4 и выше, а начиная с четвертой версии в Haxe поддерживается PHP версии 7.0 и выше.
Вы спросите: зачем PHP-разработчику использовать Haxe?
Основная причина для этого — возможность использовать одну и ту же логику и на сервере и на клиенте.
Рассмотрим такой пример: у вас есть сервер, написанный на PHP с использованием фреймворка Laravel, и несколько клиентов, написанных на JavaScript, Java, C#. В таком случае для обработки сетевого взаимодействия между сервером и клиентом (логика которого по идее одна и та же) от вас потребуется написать 4 реализации для каждого из используемых языков. Но с помощью Haxe вы можете написать код сетевого протокола один раз и затем скомпилировать/транслировать его под разные платформы, значительно сэкономив на этом время.
Вот еще пример — игровое приложение — клон Clash of Clans. Александр участвовал в разработке подобной игры: ее сервер был написан на PHP, мобильный клиент — на C# (Xamarin), а браузерный клиент — на JavaScript с использованием фреймворка Phaser. На клиенте и сервере обрабатывалась одна логика — так называемая "боёвка", которая просчитывала поведение юнитов игроков на локации. Изначально код боёвки был написан для каждой из платформ по-отдельности. Но со временем (а проект развивался около 5 лет) накапливались различия в ее поведении на сервере и на клиентах. Связано это было с тем, что писали ее разные люди, каждый из которых реализовывал ее по-своему. Из-за этого не было надежного способа обнаружения читеров, т.к логика на сервере вела себя совсем не так, как на клиенте, в результате страдали и честные игроки, т.к. сервер мог посчитать их читерами и не засчитать результаты честного боя.
В конце концов было принято решение перевести боёвку на Haxe, что позволило полностью решить проблему с читерами, т.к. теперь логика боёвки вела себя одинаково на всех платформах. Кроме того, данное решение позволило сократить расходы на дальнейшее развитие боевой системы, т.к. теперь было достаточно одного программиста, знакомого с Haxe, вместо трех, каждый из которых отвечал бы за свою платформу.
В чем Haxe и PHP отличаются друг от друга? В общем, синтаксис Haxe не покажется PHP-программисту чем-то чужеродным. Да, он отличается, но в большинстве случаев он будет очень похож на PHP.
На слайде показано сравнение кода для вывода строки в консоль. В Haxe код выглядит практически так же, за исключением того, что для работы с функциями PHP необходимо их импортировать (см. первую строку).
А вот так выглядит сгенерированный PHP-код в сравнении с написанным вручную.
Компилятор Haxe автоматически добавляет в код блок комментариев с описанием типов аргументов функций и возвращаемыми типами, поэтому полученный код можно подключить к PHP-проекту и для него прекрасно будет работать автодополнение.
Рассмотрим некоторые существенные отличия в синтаксисе Haxe и PHP.
Начнем с отличий в синтаксисе анонимных функций (на примере их использования для сортировки массива).
На слайде показана анонимная функция, которая захватывает и изменяет значение локальной переменной desc
. В PHP для этого необходимо явно указать, какие переменные доступны в теле анонимной функции. Кроме того, чтобы иметь возможность изменять значение переменной, необходимо добавить &
перед ее именем.
В Haxe такая необходимость отпадает, т.к. компилятор сам определяет, к каким переменным вы обращаетесь. Кроме того, в Haxe 4 появились стрелочные функции (краткая форма описания анонимных функций), используя которые можно сократить наш пример с сортировкой массива всего до одной строки.
Еще одно отличие в синтаксисе — это различия в описании управляющей конструкции switch
. В PHP switch
работает так же как и в Си. В Haxe он работает иначе:
- во-первых, в switch не используется ключевое слово
break
- во-вторых, можно объединять несколько условий с помощью
|
(а не дублировать ключевое словоcase
) - в-третьих, в Haxe для
switch
используется pattern matching (механизм сопоставления с образцом). Так, например, можно применитьswitch
к массиву, и в условиях можно определить действия в зависимости от содержимого массива (знак_
означает, что данное значение нас не волнует и может быть любым). Условие[1, _, 3]
будет выполнено если массив состоит из трех элементов, при этом первый элемент равен 1, третий — 3, а значение второго — любым.
В Haxe все является выражением, и это позволяет писать более компактный код. Можно вернуть значение из try
/catch
, if
или switch
, не используя ключевое слово return
внутри данных конструкций. В приведенном примере с try
/catch
компилятор "знает", что вы хотите вернуть некоторое значение, и сможет передать его из данной конструкции.
PHP постепенно движется в направлении более строгой типизации, но в Haxe уже есть строгая статическая типизация!
В приведенном примере мы присваиваем переменной s
значение, полученное из функции functionReturnsString()
, которая возвращает строку. Таким образом, тип переменной s
— строка. И если попытаться передать ее в функцию giveMeInteger()
, ожидающую в качестве аргумента целое число, то компилятор Haxe выдаст ошибку о несоответствии типов.
Этот пример также демонстрирует еще одну важную особенность Haxe — выведение типов (type inference) — способность компилятора самостоятельно определять типы переменных в зависимости от того, какое значение ей присвоить.
Для программиста это означает, что в большинстве случаев явно указывать типы переменных необязательно. Так в приведенной функции isSmall()
компилятор определит, что тип аргумента a
— целое число, т.к. в первой строке тела функции мы сравниваем значение a
с целым числом. Далее компилятор на основании того, что во второй строке тела функции мы возвращаем true
, определяет, что возвращаемый тип — булева величина. И т.к. компилятор уже определил тип возвращаемого значения, то при дальнейших попытках вернуть из функции какой-либо другой тип, он выдаст ошибку несоответствия типов.
В Haxe, в отличие от PHP, нет автоматического преобразования типов. Например, в PHP возможно вернуть строку из функции, для которой указано, что она возвращает целое число, в таком случае при выполнении скрипта строка будет преобразована в число (не всегда успешно). А в Haxe аналогичный код попросту не скомпилируется — компилятор выдаст ошибку несоответствия типов.
Еще одним отличием Haxe является его расширенная система типов, включающая в себя:
- типы функций, состоящие из типов аргументов функции и возвращаемого типа
- обобщенные (параметризированные) типы (к ним, например, относятся массивы, для которых в качестве параметра используется тип хранимых значений)
- перечисляемые типы
- обобщенные алгебраические типы данных
- типы анонимных структур позволяют объявлять типы для объектов без объявления классов
- абстрактные типы данных (абстракции над существующими типами, но без потери производительности в рантайме)
Все перечисленные типы при компиляции преобразуются в PHP-классы.
Одной из главных отличительных особенностей Haxe является метапрограммирование (в Haxe оно называется макросами), то есть возможность автоматической генерации исходного кода программы.
Макросы выполняются во время компиляции программы и пишутся на обычном Haxe.
Макросы имеют полный доступ к абстрактному синтаксическому дереву, то есть могут читать (искать в нем требуемые выражения) и изменять его.
Макросы могут генерировать выражения, изменять существующие типы, а также создавать новые.
В контексте PHP макросы могут например использоваться для роутинга. Скажем, у вас есть простой класс-роутер, в котором реализованы метод для отображения страницы по ее идентификатору, а также метод для выхода из системы. Используя макрос, можно сгенерировать код для метода route()
, который на основании http-запроса будет перенаправлять его в соответствующий метод класса Router
. Таким образом, отпадает необходимость вручную писать if’ы для вызовов каждого из методов данного класса (макрос сделает это автоматически при компиляции проекта в PHP). Заметьте, что полученный код не использует рефлексию, не требует никаких специальных конфигурационных файлов, или каких-либо еще дополнительных ухищрений, поэтому работать будет он очень быстро.
Еще один пример использования макросов — генерация кода для парсинга и валидации JSON. К примеру, у вас есть класс Data
, объекты которого должны создаваться на основании данных, получаемых из JSON. Это можно сделать с помощью макроса, но так как у макроса есть доступ к структуре класса Data
, то в дополнение к парсингу можно сгенерировать код для валидации JSON, добавив выброс исключений при отсутствии полей или несоответствии типа данных. Таким образом, можно быть уверенным в том, что ваше приложение не пропустит некорректные данные, получаемые от пользователей или от стороннего сервера.
Стоит также упомянуть важные особенности реализации некоторых типов данных для платформы PHP, т.к. если не учитывать их, то можно столкнуться с неприятными последствиями.
В PHP строки бинарно-безопасные (binary safe), поэтому в PHP, если вам не требуется поддержка Юникод, методы для работы со строками работают очень быстро.
В Haxe, начиная с четвертой версии, строки поддерживают Юникод, поэтому при их компиляции в PHP будут использоваться методы из модуля для работы с многобайтовыми строками mbstring, а это означает медленный доступ к произвольным символам в строке, медленное вычисление длины строки.
Поэтому если поддержка Юникода вам не нужна, то для работы со строками можно использовать методы класса php.NativeString
, которые будут использовать "родные" строки из PHP.
На слайде слева представлен код на PHP, который использует как методы, поддерживающие Юникод, так и нет.
Справа представлен эквивалентный код на Haxe. Как видно, если вам нужна поддержка Юникод, то необходимо использовать методы класса String
, если нет — то методы класса php.NativeString
.
Еще один важный момент — это работа с массивами.
В PHP массивы передаются по значению, также массивы поддерживают как числовые, так и строковые ключи.
В Haxe массивы передаются по ссылке и поддерживают только числовые ключи (если необходимы строковые ключи, то в Haxe для этого следует использовать класс Map
). Также в Haxe-массивах не допускаются "дыры“ в индексах (индексы должны идти непрерывно).
Также стоит отметить, что запись в массив по индексу в Haxe довольно медленная.
Здесь приведен код для "маппинга" массива с использованием стрелочной функции. Как видно, компилятор Haxe довольно активно оптимизирует PHP-код, получаемый на выходе: анонимная функция в полученном коде отсутствует, вместо этого ее код применяется в цикле к каждому элементу массива. Кроме map()
такая оптимизация применяется и к методу filter()
.
Также при необходимости в Haxe можно использовать класс php.NativeArray
и соответствующие методы PHP для работы с массивами.
Анонимные объекты реализованы с помощью класса HxAnon
, который наследуется от класса StdClass
из PHP.
В PHP StdClass
— это класс, в экземпляры которого превращается всё, что мы пытаемся конвертировать в объект. И он бы идеально подошёл для реализации анонимных объектов, если бы не одна особенность их спецификации: в Haxe обращение к несуществующему полю анонимного объекта должно возвращать null
, а в PHP это выкидывает warning. Из-за этого пришлось отнаследоваться от стандартного класса из PHP и добавить ему магический метод, который при обращении к несуществующим свойствам возвращает null
.
Haxe может взаимодействовать с кодом, написанным на PHP. Для этого имеются следующие возможности (аналогичные возможностям взаимодействия с JavaScript-кодом):
- экстерны (externs)
- вставки PHP-кода напрямую в Haxe-код
- специальные классы из пакета
php.*
php.Syntax
— для специальных конструкций PHP, которых нет в Haxephp.Global
— для "нативных" глобальных функций PHPphp.Const
— для "нативных" глобальных констант PHPphp.SuperGlobal
— для сверхглобальных переменных PHP, доступных отовсюду ($_POST
,$_GET
,$_SERVER
и т.п.)
Т.к. PHP использует классическую ООП-модель, то написание экстернов для него — довольно простой процесс. Фактически экстерны для PHP-классов — это практически дословный "перевод" в Haxe, за исключением некоторых ключевых слов.
В качестве примера так будет выглядеть код экстерна для PHP-класса со слайда выше:
Константы из PHP-класса "превращаются" в статические переменные в Haxe-коде (но с добавлением специальных мета-тэгов).
Статическая переменная $useBuiltinEncoderDecoder
становится статической переменной useBuiltinEncoderDecoder
.
Отсюда видно, что экстерны для PHP-классов можно создавать автоматически (Александр планирует реализовать генератор экстернов в этом году).
Для вставок PHP-кода используется специальный модуль php.Syntax
. Код, добавляемый таким способом, не подвергается никаким преобразованиям и оптимизациям со стороны компилятора Haxe.
Кроме php.Syntax
в Haxe осталась возможность использования untyped-кода.
Также стоит упомянуть такие особенности Haxe, которых нет в PHP:
- реальные свойства с методами для чтения и записи
- поля и переменные, доступные только для чтения
- статические расширения
- мета-тэги, которые можно использовать для аннотации полей и которые доступны для чтения в макросах. Таким образом, мета-тэги используются в основном для метапрограммирования
- Null-безопасность (экспериментальная функция)
- условная компиляция, которая позволяет включать/отключать части кода, которые могут быть доступными, например, в отладочной версии приложения
- компилятор Haxe генерирует высоко-оптимизированный PHP-код, который может работать в разы быстрее PHP-кода, написанного вручную.
Больше информации о Haxe и о его функциях доступно в его официальном руководстве.
Комментарии (25)
L0NGMAN
30.06.2019 02:32В PHP тоже нет автоматическое преобразование типов, если использовать declare(strict_types=1); в начале файла.
khrnsb4y
30.06.2019 12:49Все немного не так. При использовании директивы получим TypeError при несоответствии типов. Без нее как раз будет попытка приведения. Приведение там довольно своеобразное. Можем передавать число вместо строки, int вместо float, или наоборот, но не более.
Taraflex
30.06.2019 02:40во-вторых, можно объединять несколько условий с помощью | (а не дублировать ключевое слово case)
Кошмар какой. Это с какой версии? Давно не в теме. Раньше же использовалась запятая вместо | ( old.haxe.org/ref/syntax?lang=ru#switch ). Что за помутнение заставило Канасье такое запилить?
Судя по try.haxe.org/#D4A66 даже оборачивание в скобки не помогает.Zaphy Автор
30.06.2019 08:21Скорее всего с третьей версии, которая вышла в 2013 году. Так что довольно давно.
ReallyUniqueName
30.06.2019 09:02Насколько я помню, этот синтаксис появился одновременно с запятыми или даже чуть раньше. Потому что он позаимствован из окамла (на котором пишет Канасье — создатель языка), где в паттерн-матчинге используется именно вертикальная черта для разделения вариантов.
Что касается вашего примера, то не понятно, чему не помогает оборачивание в скобки?
Math.random()
возвращает Float в пределах от 0 до 1. Поэтомуcase 1 | 2
никогда не совпадёт с его результатом.Taraflex
30.06.2019 11:52Haxe компилятор не анализирует диапазон 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;
для варианта со скобками.
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 )phantom-code
30.06.2019 11:41+1Ben Morris долго пользовался Haxe для геймдева и в итоге создал свой язык — kit.
Zoolander
30.06.2019 13:36посмотрел и в контексте с вышеприведенным BunnyMark повторю — high performance в играх и приложениях является критическим в узком сегменте, да и там часто тормоза возникают не из-за того, что язык или платформа плохо выводит спрайты, а потому что
— используются живые частицы там, где достаточно спрайтовых анимаций
— при заходе в дом продолжает рендериться весь игровой мир (еще в Серьезном Сэме мододелы боролись с этим, вручную закрывая двери после захода в комнату)
— из-за запутанной архитектуры для мелких частных действий вызывается куча перерасчетов в массе объектов, которые вообще никак не должны реагировать
— разработчик платформы оптимизировал ее под определенные тесты (или писал тесты под платформу), а в вашей игре совсем другие требования и оптимизация разработчика только все усложняет — лучше бы он писал более абстрактно
— дизайнер игры нарисовал такое, что плохо ложится на платформу в плане скорости
наибольшая проблема performance — это performance самой разработки )
dmitryhryppa
30.06.2019 15:56Но он это сделал не потому, что Haxe плохой, а просто потому, что у него свое видение развития языка. Имхо, конечно, но мне кажется, что его язык применим только в ограниченом типе проектов и имеет абсолютно другую философию, т.к. это своего рода очередной D без GC.
В то время как Haxe — это не только игры, но и широкий спектр других проектов: фронтенд, бекенд, десктопные и мобильные приложения, консольные утилиты и все те же игры где он уже зарекомендовал себя отличным инструментом (см. выпущенные игры на Haxe).
embden
30.06.2019 16:45+1А можете описать свой воркфлоу с Haxe? Что использовали, какая интеграция с IDE и есть ли различные анализаторы (статические, динамические)?
Zoolander
01.07.2019 04:03workflow был очень прост — FlashDevelop с минимумом подсказок + Android SDK
это сейчас я избалован продуктами от JetBrains, а тогда я смело набирал без всяких интеллектуальных подсказок и думал, что все хорошо ))
PS: говорят, что есть хорошая интеграция с Visual Studio и там есть даже плагин специальный, но я не проверял.pecheny
01.07.2019 11:26+1Вот со студией как раз вообще никакой интеграции нет. У MS есть совершенно отдельный кроссплатформенный редактор VSCode – речь о нем. Для нее MS придумали очень классную штуку – поддержку language server, который позволяет всю интеллектуальную поддержку языка вынести наружу и легко интегрировать. В случае с Haxe так и сделано: сам компилятор, который знает все о типах (даже тех, которые генерируются макросами в процессе компиляции), обеспечивает поддержку языка. Поэтому в VSCode, наравне с другими редакторами, поддерживающими lang серверы, поддержка haxe на уровне.
FlashDevelop, который сейчас также известен как HaxeDevelop, вроде бы тоже имеет интеграцию компиляторной помощи; а так же много всяких удобных штук для haxe. Он продолжают развиваться.
Ну и наконец, люди, избалованные JetBrains, могут поставить отличный haxe-плагин, у которого как раз на днях вышел новый релиз. Я пользуюсь именно этим вариантом.
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()
Zoolander
01.07.2019 04:06похоже на реализацию некоторых функций, которых нет (или якобы нет) в Lua
в JavaScript похожая штука с полифиллами и библиотеками — но Hello World там пишется намного чище
пожалуй, наиболее близкая аналогия — Kotlin, еще один траспайлерный язык, который при переводе в JavaScript добавляет еще свой рантайм — несмертельно большой, но видимый
тут надо смотреть на рабочих проектах и вообще надо смотреть — новые функции это хорошо или плохо?pecheny
01.07.2019 11:42Да, часто языки, имеющие таргетами другие языки, тащат за собой рантайм и std-либу, которые могут весить очень много. Когда-то давно смотрел, что scala-js выдавала helloworld на несколько мегов.
В случае Haxe, ему приходится обеспечивать одинаковую работу строк, массивов, трейсов и прочего на всем заопарке целевых платформ (а заопарк впечатляющий).
Поэтому рантайм в пару десятков кб – довольно скромно. Это не множитель к размеру, а слагаемое. На helloworld-ах его вклад, конечно, наиболее заметен.
У Haxe в отличие от многих транспайлеров есть отличный dce, который вычищает почти весь неиспользуемый код, поэтому в результат попадает лишь необходимый минимум стандартной библиотеки.
embden
01.07.2019 23:49Вот почему у меня Haxe не начался. Во-первых, почему-то основная среда разработки кросс-платформенного языка Windows-only. Во-вторых, на Linux нельзя поставить решение "из коробки" из репозитория. Захотел я попробовать Haxe, а из репозиториев можно только вим с плагином поставить. В-третьих, при заходе на Discord для получения помощи надо пройти регистрацию. К тому же сам дискорд не всем нравится (почему бы не сделать транспорт из IRC или matrix). В-четвертых, почему-то качество туториалов довольно низкое. Вот, к примеру, введение.
Вроде бы это всё и мелочи, а желание разбираться с Haxe и разработкой игр не нём пропало.
pecheny
02.07.2019 01:08+1Что такое «основная среда», и о какой среде речь? Выше в ветке были названы три распространенных варианта, два из которых кроссплатформенные. Этак и до плюсов докапаться можно из-за студии. Какое вообще отношение язык имеет к дистрибьюции VSCode или Intellij IDEA?
Документация по самому языку довольно неплохо написано, много хороших и интересных примеров можно найти в разделе cookbook.
Что до стартовых туториалов, особенно в контексте разработки игр – с haxe слишком много вариантов того, как начать, и в какую сторону двигаться. Гида по этим вариантам, действиетльно, не хватает. А сами туториалы есть не только у языка, но и у игровых движков; для старта может оказаться полезным смотреть на них. Каналов для общения хватает: есть и официальны форум, гитхаб, сообщества игровых движков. В русскоязычном сегменте есть скайп и телеграм-чаты.
Чтобы было более понятна ситуация приведу пример с шарпом, на котором тоже можно делать игры. Делать можно на юнити или на моногейм, а Introduction to the C#с MSDN не слишком поможет стартануть.embden
02.07.2019 01:35Что такое «основная среда», и о какой среде речь?
Я про Haxedevelop.
Этак и до плюсов докапаться можно из-за студии. Какое вообще отношение язык имеет к дистрибьюции VSCode или Intellij IDEA?
Самое прямое — чем легче начать использовать язык или фреймворк, тем больше вероятность его распространения. Чем легче и интереснее туториалы, тем выше вероятность того, что языком человек заинтересуется. Взять, к примеру, godot-engine. В его туториалах с первых же шагов делают игру! Чтобы начать им пользоваться, надо просто скачать и распаковать бинарник. Я сам с удовольствием прошёл этот туториал. А в Haxe сначала надо выбрать среду разработки (я не хочу устанавливать на личном компьютере софт не из репозиториев), потом выбрать движок, потом учиться на мануалах, а не на туториалах. И вот даже выбрал я HaxeFlixel, открываю туториал, а там говорится, что надо установить FlashDevelop, а он Windows-only.
Что до стартовых туториалов, особенно в контексте разработки игр – с haxe слишком много вариантов того, как начать, и в какую сторону двигаться. Гида по этим вариантам, действиетльно, не хватает.
Вот мне не удалось даже начать, при этом с Godot начать удалось, с libsdl тоже, а вот с Haxe оказалось сложнее.
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/
embden
02.07.2019 11:04Так а в чем Вы меня хотите переубедить? Я захотел попробовать, у меня это сделать быстро и легко не получилось. Устанавливать программы не из репозитория, плагины к ним, искать хорошие туториалы (вместо тех мануалов, что на сайте) — не у каждого хватит мотивации.
Взять тот же heaps, смотрим его туториал Hello World. Сначала создать xml-конфиг, добавить текст программы, потом создать build task в vscode (что делать пользователям других IDE?) — уже на этом этапе часть пользователей подумает, что фреймворк излишне запутанный (не сложный). В нормальных случаях для того, чтобы запустить Hello World, надо выбрать соответствующий пример из базы примеров и нажать 'Run'.
Я уверен, что Haxe это сочетание отличной идеи и отличной реализации, только вот быть хорошим продуктом недостаточно, необходимы еще, как минимум, хорошие туториалы и низкий порог вхождения (потому что у конкурентов он достаточно низкий).
PerlPower
Хороший язык. Вот честно сказать еще лет 10 назад выглядел как сейчас Rust на замену C/C++, только для всякой скриптовщины. На за все 10 лет что я его периодически мониторю, он так и не взлетел. И мне интересно почему.
pecheny
Не могу согласиться про скриптовщину, если правильно понял, о чем речь. Haxe никогда не позиционировался ни как «легковстраиваемый рантайм», ни как «shell-ориентированный интерпретатор». Если рассматривать кросс-компиляцию в другой скриптовый язык, то это всегда будет усложнением системы — даже при наличии своих плюсов, не панацея.
А «не взлетел» он, скорее всего, потому, что нет того, что у HaxeFoundation нет продавана. Самый заметный продаван – Джошуа, но он продвигает OpenFL, из-за чего, к сожалению, Haxe ассоциируется именно с ним.
Язык мощный, но у него своя ниша. Не всегда там, где он мог бы быть полезен, про него знают/понимают.