Haxe как идеальный язык для разработчика full stack. Часть 1. JavaScript

Привет, меня зовут Дмитрий, я занимаюсь разработкой кросс-платформенных решений, игр, веб-сервисов и мобильных приложений. В этом мне очень помогает язык Haxe, который позволяет шире взглянуть на понятие full stack.

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

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

Также имейте в виду, что я буду использовать язык Haxe версии 4, который на момент написания этой статьи находится в версии release candindate.

В качестве редактора я использую VS Code с официальным плагином.

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

Практическое знакомство с Haxe мы начнем через одну из самых популярных целевых платформ — JavaScript. Дальше, разобравшись с языком, углубимся в специфику отдельно взятых таргетов, в частности C++, Java (и JVM), C# и других. А потом, возможно, если это будет интересно сообществу, напишем какой-нибудь реальный проект.

Вступление

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

Примечание. В большинстве случаев компилятор Haxe — это на самом деле транслятор (transpiler). Однако в официальной документации используется именно термин «компилятор», так что мы тоже будем употреблять его. В дальнейшем вы поймете, почему границы между этими понятиями в контексте Haxe размыты.

Итак, вернемся к теме статьи — JavaScript.

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

Если вам комфортнее писать на строго типизированном языке, то Haxe будет для вас отличной альтернативой, даже без привязки к тому, что на нем можно писать кросс-платформенный код.

Haxe научился компилироваться в JS еще в 2006 году, задолго до появления TypeScript и Dart. Но, к сожалению, он выпал из информационного пространства, и далеко не каждый в курсе, что такой язык вообще существует. Именно потому я и пишу эти статьи: чтобы рассказать вам об инструменте, который незаслуженно находится в тени.

Однако пусть малая известность вас не пугает. Haxe — взрослый инструмент, а не фреймворк-однодневка. И, в отличие от TS и Dart, Haxe справляется с некоторыми задачами лучше и быстрее. Поэтому давайте для начала взглянем на следующий бенчмарк (ссылка на GitHub — внизу статьи), в котором тестируется декодирование изображения.

langcompilation timechrome run timefirefox run timesizeminimified size
Haxe0.22s6.76s8.15s27KB* 13KB
TypeScript2.79s7.86s8.46s12KB 7KB
Dart5.38s9.137s8.8s98KB 89KB
Wasm8.74s6.8s5.93s82KB** 69KB

Автор этого бенчмарка протестировал Haxe, TypeScript, Dart и Wasm на одной и той же задаче. Как видите, скорость компиляции Haxe в десятки раз выше, чем у конкурентов. Haxe компилирует выходной файл очень маленького размера, а по времени выполнения кода он один из лучших, проигрывая лишь Wasm в Firefox.

Конечно, скажете вы, это все «синтетика». Но, уверяю вас: на практике Haxe даст вам множество способов оптимизации вашего кода. Более того, в некоторых случаях код, написанный на Haxe и транслированный в JS, может выполняться быстрее, чем аналогичный код, написанный вручную на чистом JS. Все это возможно благодаря оптимизирующему компилятору, статическому анализатору и языковым конструкциям, которые позволяют писать код, максимально оптимизированный под каждую платформу.

О качестве генерации кода

Давайте рассмотрим следующий искусственный пример, демонстрирующий некоторые возможности в плане оптимизации генерируемого кода. К слову, этот пример справедлив и для других таргетов. Мы будем рассматривать его и в следующих статьях.

Если вы захотите опробовать код самостоятельно, то вам понадобится установленный в системе Node.js. Итак, создайте файл модуля Main.hx и поместите в него следующий код:

class Main {
  // Точка входа
  // Тип возвращаемого значения функции писать необязательно. В Haxe есть выведение типов из выражения.
  // Возвращаемый тип функции будет Void, потому что мы ничего в ней не возвращаем.

  static function main() {
    // Создадим контейнер с пользователями.
    // Указывать тип переменной, как и в случае с функцией, необязательно.
    final users = new Users();
    for (i in 0…10) {
    // Добавим 10 пользователей со случайным возрастом:
    users.push(new User(inline Std.random(99)));
  }

    // Выведем только взрослых пользователей в консоль:
    users.printOnlyAdultUsers();
  }
}

// Класс нашего пользователя.
// Сделаем все поля класса публичными (вместо ключевого слова `public`, объявленного возле каждого поля отдельно):
@:publicFields
class User {
  final age:Int;
  function new(age:Int) {
    this.age = age;
  }

  inline function toString():String {
    return 'User(age = $age)';
  }
}

// Наш контейнер с пользователями (обратите внимание, что это абстрактный тип):
@:forward(push, length)
abstract Users(Array<User>) {
  // Обратите внимание на ключевое слово inline:
  static inline final ADULT_AGE_VALUE:Int = 18;
  // Если тело функции содержит 1 строчку, то указывать {} скобки необязательно:
  public inline function new() this = [];

  public inline function printOnlyAdultUsers():Void {
    for (user in this) {
      if (user.age >= ADULT_AGE_VALUE) {
        trace(user.toString());
      }
    }
  }
}

Обратите внимание на абстрактный тип Users. В Haxe это не совсем то, что вы обычно представляете себе в других языках программирования. Абстрактный тип Haxe — это тип, который в рантайме является каким-либо другим типом. Фактически это обертка какого-то типа, если хотите.

В нашем случае мы оперируем более высокоуровневой абстракцией Users, но на практике это всего лишь массив, и в рантайме он также будет массивом. Это дает большой простор для оптимизации выходного файла.

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

Вы можете скопировать приведенный выше Haxe-код и собрать его со следующими параметрами в файле build.hxml для достижения аналогичного результата:

# Класс с методом main
-m Main
# Выходной файл
-js ./main.js
# Флаг для удаления неиспользуемого кода
-dce full
# Флаг для генерации ES6-классов (по умолчанию ES5)
-D js-es=6
# Включение статического анализатора
-D analyzer-optimize

Убедитесь, что Main.hx и build.hxml лежат в одной директории. Сочетание Ctrl+Shift+B в VS Code вызовет меню компиляции. Выберите в выпадающем списке ваш build.hxml для продолжения.

Подробнее об hxml-файлах и компиляции Haxe читайте в предыдущей статье.

Итак, скомпилировав проект, на выходе мы получим вот такой JavaScript-код (ES6 для более удобного чтения, по умолчанию Haxe генерирует ES5):

class Main {
  static main() {
    // Класс `Users` был упразднен до обычного массива:
    var users = [];
    // Цикл for был полностью развернут (так происходит не всегда, к слову, глубина развертывания может быть задана опционально,
    // а тело метода `Std.random()` было помещено в место его вызова (inline-вызов):
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));
    users.push(new User(Math.floor(Math.random() * 99)));

    // Метод `printOnlyAdultUsers` класса Users
    // был упразднен и помещен в место его вызова:
    var _g = 0;
    while(_g < users.length) {
      var user = users[_g];
      ++_g;
      // Константа ADULT_AGE_VALUE была упразднена.
      if (user.age >= 18) {
        // Метод toString класса User был упразднен и помещен в место вызова:
        console.log("Main.hx:32:", "User(age = " + user.age + ")");
      }
    }
  }
}
class User {
  constructor(age) {
    this.age = age;
  }
  // Метод toString() был убран из реализации, так как его содержимое генерируется прямо в место вызова и в рантайме, в классе User, он нам будет не нужен. Это позволяет уменьшить размер выходного файла.
}

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

Использование Haxe-классов в JavaScript

Представим ситуацию: вы решили написать некую библиотеку на Haxe и хотите иметь возможность использовать ее в JavaScript-проектах. Однако по умолчанию Haxe-классы скрыты извне, и получить доступ к ним у вас не получится. Для таких задач в Haxe есть специальная мета — expose.

Давайте заменим код класса Main из предыдущего примера следующим:

class Main {
  static function main() {

  }
  // Отмечаем функцию как видимую извне
  @:expose
  public static function printText(text:String):Void {
    trace(text);
  }
}

Скомпилируем его, а затем создадим в корне нашего проекта файл test.js. Импортируем туда наш сгенерированный Haxe’ом JavaScript:

const hx = require("./main.js").Main;
hx.printText("Hello!");

Запускаем: node./test.js

И в консоли видим: Main.hx:8: Hello!

Отлично! Обратите внимание, что метатег expose вы можете использовать как на поле класса, так и на самом классе.

Давайте вернемся на пару шагов назад и снова взглянем на сгенерированный код в main.js:

class Main {
  static main() {
  }
  static printText(text) {
    console.log("Main.hx:8:",text);
  }
}
$hx_exports["Main"] = Main;
Main.main();

Как видите, помимо нашей новой функции, здесь присутствует еще и стандартная точка входа — функция main. Но в некоторых случаях мы хотим генерировать код без нее — так, будто это полноценный API, где не должно быть ничего лишнего. Поэтому давайте удалим пустую функцию main.

Однако если вы попробуете скомпилировать проект на этом этапе, то получите ошибку: Haxe не может найти точку входа в приложение. Чтобы обойти это, нам нужно изменить файл с инструкциями для компилятора (наш build.hxml) и убрать оттуда строчку -m Main, заменив ее просто Main. Так мы скажем компилятору, что хотим экспортировать класс Main без привязки к какому-то конкретному главному классу приложения, в котором содержится точка входа.

Проделав эти манипуляции, мы можем смело компилировать код, получив на выходе то, что и требовалось:

class Main {
  static printText(text) {
    console.log("Main.hx:4:",text);
  }
}

Ничего лишнего!

Примечание. Внимательный читатель, наверное, уже обратил внимание на всю мощь этого подхода. Ведь Haxe умеет компилироваться во множество языков, а не только в JS! Перед вами открывается мир кросс-платформенной разработки. Написав бизнес-логику один раз, без привязки к платформе, вы сможете импортировать ее в проекты на абсолютно разных платформах! Скажем, в front-end на JS, сервер на PHP или Java. Однако нюансы работы с другими языками будут описаны в будущих статьях.

Использование JavaScript-классов в Haxe

Естественно, современный JS — это бездонная папка с зависимостями :) И если вы решили писать проект на Haxe, то непременно захотите воспользоваться сторонними библиотеками.

Создайте файл api.js с таким содержимым:

class Api {
  static printText(text) {
    console.log("api.js : " + text);
  }
};
module.exports.Api = Api;

Теперь нам нужно как-то подключить его в Haxe. Для этого есть несколько способов, и они зависят от вашего проекта.

Замените модуль Main.hx следующим:

// Всем известный require в JS:
@:jsRequire("./api.js", "Api")
// Обратите внимание, что класс объявлен с ключевым словом extern (т. е. "внешний"):
extern class Api {
  // Объявляем биндинги к функции:
  public static function printText(text:String):Void;
}

class Main {
  static function main() {
  // Метод принимает только строку. Ошибки с типами, как в JS, исключены.
    Api.printText("This is a text");
  }
}

Скомпилируем и взглянем на сгенерированный main.js:

var Api = require("./api.js").Api;
class Main {
  static main() {
    Api.printText("This is a text");
  }
}
Main.main();

Запустим и увидим в консоли This is a text. Все выглядит так, как и планировалось, и теперь мы можем вызвать JavaScript-функцию из сторонней библиотеки.

Стоит сказать, что, помимо этого, Haxe может встроить JS-файл и на лету склеить его с вашим кодом. Просто добавьте --macro haxe.macro.Compiler.includeFile("api.js") в ваш hxml-файл, и содержимое api.js будет встроено сразу в main.js.

Вставки JavaScript-кода

Бывают случаи, когда вам нужно сделать вставки кода на JavaScript (или любого другого таргета Haxe) прямо в место вызова. И Haxe это позволяет!

В разрезе этой задачи мы рассмотрим еще один способ работы с внешними классами — без типизации и написания биндингов (в терминологии Haxe — экстернов). Для этого видоизмените модуль Main.hx до следующего вида:

// Обратите внимание, что вы можете импортировать функцию.
import js.Syntax.code;
class Main {
  static function main() {
    // Просто вставляем JavaScript-код:
    code("require('./api.js').Api.printText('Test 1')");

    // То же самое с захватом переменной:
    code("require('./api.js').Api.printText({0})", "Test 2");

    // То же самое с привязкой к Haxe-переменной:
    final printText1 = code("require('./api.js').Api.printText");
    printText1("Test 3");

    // То же самое с привязкой к Haxe-переменной и ее последующей типизацией:
    final printText2:(String)->Void = code("require('./api.js').Api.printText");
    // Теперь мы защищены от ошибки, связанной с типами передаваемых аргументов:
    printText2("Test 4");
  }
}

Как и прежде, я оставил комментарии, описывающие, что именно происходит в коде. Для сравнения посмотрим на сгенерированный JS:

class Main {
  static main() {
    require('./api.js').Api.printText('Test 1');
    require('./api.js').Api.printText("Test 2");
    require('./api.js').Api.printText("Test 3");
    require('./api.js').Api.printText("Test 4");
  }
}
Main.main();

Результат работы кода:

api.js : Test 1
api.js : Test 2
api.js : Test 3
api.js : Test 4

Еще раз рассмотрим пример, где мы выводим строку «Test 3»:

final printText1 = code("require('./api.js').Api.printText");
printText1("Test 3")

Мы присваиваем JS-функцию Haxe-переменной, и в данном случае компилятор не знает, какого типа наша исходная JS-функция, а потому не может корректно вывести тип этой Haxe-переменной. Таким образом, ей присваивается тип Dynamic — это динамический, небезопасный тип. Читай, обычная переменная в JS-мире, которая может быть чем угодно и содержать какие угодно поля.

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

Так, в следующей строке мы уже написали более безопасный код:

final printText:(String)->Void = code('require("./api.js").Api.printText');
printText("Test 4");

Мы явно указали тип, сообщив компилятору о том, что printText — функция, которая принимает строку и ничего не возвращает. Теперь мы можем избежать ошибок, связанных с типами, еще на этапе компиляции.

Отладка Haxe и JavaScript

Haxe умеет генерировать карты исходников (source maps) для JavaScript, а значит, может и отлаживаться. Подробнее — здесь.

Если вкратце, у вас в арсенале есть два флага:

  • —debug — для формирования debug-сборки и автоматической генерации source maps;
  • —D js-source-map — для генерации source maps в релизной сборке.

Для отладки Node.js достаточно воспользоваться встроенным Node-отладчиком в VS Code. Добавьте -debug в ваш hxml-файл .

Добавьте задачу в tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "haxe",
      "args": "active configuration",
    "group": {
      "kind": "build",
      "isDefault": true
      },
      "label": "haxe-build"
    }
  ]
}

Подробнее про задачи в VSCode.

Добавьте конфигурацию в launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceFolder}/main.js",
      "preLaunchTask": "haxe-build"
    }
  ]
}

Подробнее про конфигурации запуска в VSCode .

В конфигурации launch.json мы указали нашу задачу из tasks.json, которая будет компилировать Haxe-код перед запуском отладки сгенерированного main.js.

Готово! Ставьте брейкпоинт и жмите F5!

Макросы

Одна из киллер-фич языка — генерация кода во время компиляции через так называемые макросы. Если вкратце, макрокод — это код, который выполняется во время компиляции, и это то место, где вы можете получить доступ к типизированному абстрактному синтаксическому дереву и повлиять на его ветки.

Давайте опробуем их на самом простом примере. Создайте модуль Config.hx с таким кодом:

import haxe.Json;
import haxe.macro.Expr;
import haxe.macro.Context;
class Config {
  public macro static function get(file:ExprOf<String>):ExprOf<ConfigDef> {
    final pos = Context.currentPos();
    //Присваиваем значение прямо из блока try. Потому что в хаксе все является выражение.
    final c:ConfigDef = try {
	//Перечисляем наше выражение, которое пришло извне:
      switch file.expr {
        //Если выражение - строковая константа, то мы передаем ее значение далее в getContent:
        case EConst(CString(s)): Json.parse(sys.io.File.getContent(s));
        //Во всех других случаях выбросим ошибку:
        case _: Context.fatalError("Invalid file name.", pos);
      }
    }
    catch (e:Any) {
	//Выбросим ошибку компиляции, если парсинг JSON не был успешным:
      Context.fatalError("Invalid project config or file is not exists.", pos);
    }
    //Возвращаем наш конфиг с валидацией его полей через type check:
    return macro ($v{c} : Config.ConfigDef);
  }
}

А наш модуль Main.hx исправьте следующим образом:

class Main {
  static function main() {
    trace(Config.get("./config.json").server);
  }
}

Также нам понадобится файл config.json в корне, рядом с выходным main.js:

{
  "server": "https://google.com"
}

Теперь скомпилируем и посмотрим на выходной JS:

class Main {
  static main() {
    console.log("src/Main.hx:3:", "https://google.com");
  }
}

Мы с вами прочитали JSON-файл во время компиляции, взяли из него значение и вставили в наш код. Посмотрите, никакого оверхеда в рантайме! Кроме того, вы еще и получили валидацию своего конфига на лету. Попробуйте изменить config.json , сделав там опечатку в слове server. Haxe не даст вам такое скомпилировать.

В макросах вам будет доступна вся стандартная библиотека Haxe. Вы сможете манипулировать конфигурациями, ресурсами, файлами и генерировать код на лету. Более того, вы даже сможете привносить новый синтаксис в свой Haxe-код, парсить его макросом и генерировать из него то, что вам хочется.

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

Приведу пример. В одном из своих pet-проектов я не стал использовать синтаксис Express.js один в один, как это предлагает фреймворк: хотелось больше безопасности и гибкости. И вот с помощью макросов я пришел к следующему виду роутера:

class EventsRouter implements IRouter {
  @:get("/events.get")
  function getEvent(id:String) {
    res.asJson({id: id, title: "Event Title"});
  }
}

Это компилируется в следующий JS-код:

class EventsRouter {
  constructor() {
    this.__router.get("/events.get", $bind(this, this.getEvent));
  }
  getEvent(req, res) {
    var status = {isSuccess: true, message: "Operation done."};
    if (req.query.id == null || req.query.id.length == 0) {
      res.writeHead(400, {"Content-Type": "application/json"});
      status.message = "Invalid request";
      status.isSuccess = false;
      res.end({status: status, data: null});
    }
    else {
      var id = Std.string(req.query.id);
      res.writeHead(200, {"Content-Type": "application/json"});
      res.end({status: status, data: {id: id, title: "Event Title"}});
    }
  }
}

Таким образом, макрос вместо меня делает рутинную работу: валидирует входящие данные и в случае неуспеха сигнализирует об ошибке либо же отвечает на запрос положительно, если данные валидны, а также автоматически прописывает роуты в роутер.

Итак, благодаря Haxe и макросам вы сможете автоматизировать множество процессов в своем проекте.

Конец

Экосистема Haxe довольно обширная, в ней очень много «черной магии» и нюансов работы с каждой поддерживаемой платформой. Описать все в одной статье, которая рассказала бы о Haxe от а до я и которую кто-то осилил бы за раз, невозможно.

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

Если кто-то не разобрался, как что-то скомпилировать, настроить и запустить, смело спрашивайте в комментариях.

Спасибо, что дочитали!

Полезные ссылки

Getting started with JavaScript
Мануал по Haxe
Примеры кода
Форум
Wiki по плагину VS Code
Бенчмарк со скриншота

Мои статьи о Haxe на DOU
Связь со мною в Telegramm

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному1
LinkedIn



40 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

) Респект за твій доробок. Як на мене, то HAXE — для романтиків. Писати на ньому — задоволення, особливо після C++ чи навіть новітнього JS чи TS. І це його сильна сторона. Він просто приємний, як домашній халат. І годиться більше для домашнього використання. Всі, хто висловлює негатив, просто не читали, або не дочитали, або просто програмують тільки на роботі і лише свою роботу.

Сегодня существует тысяча и одна причина не писать на JavaScript. Однако мы не будем пытаться доказать, что с JavaScript что-то не так.

не мы, а вы.
с джс все не так у тех, кому влом с ним детально разбираться

Вы же вырвали фразу из контекста. Там дальше написано, что современный стандарт весьма неплох. Потому что автор не считает, что с js что-то не так.
Вас, кроме этого, больше ничего не заинтересовало?

Є ще Nim як альтернатива, з статичною типізацією, пайтон-подібним синтаксисом і компіляцією в с/с++/obj c/js. Під нього є ліби-біндінги jnim для джави і андроід та jsbind під emscripten і asm.js для вебу. Теж з макросами з доступом в AST і викликом функцій при компіляції. Проста і елеганта мова ;)

Дійсно, є така мова. Якщо синтаксис подобається, то безумовно є сенс її розглянути. З хаксом у них багато спільних ідей. Проте, є і різниця — це важливо. Я обрав хакс :)

Спробував порівняти якість генерації JS коду.

Приклад с головної сторінки сайту Nim:

import strformat

type
  Person = object
    name: string
    age: Natural # Ensures the age is positive

let people = [
  Person(name: "John", age: 45),
  Person(name: "Kate", age: 30)
]

for person in people:
  echo(fmt"{person.name} is {person.age} years old")
З такими флагами компіляції nim js -d:nodejs .\main.nim
Генерує мені вихідний .js із 1484 рядками з на вигляд не самим оптимізованим кодом.

В той час як аналогічний Haxe код:

class Main {
	static function main() {
		final people:Array<Person> = [
			{name: "John", age: 45},
			{name: "Kate", age: 30},
		];

		for (person in people) {
			trace('${person.name} is ${person.age} years old');
		}
	}
}

typedef Person = {
	name:String,
	age:Int
};
Генерує 16 рядків і жодного оверхеду. Чистий, майже руками написаний JS, який легко читається.

P.S. Можливо у Nim є якісь флаги для оптимізації?
--opt:size не допоміг, іншого не знайшов.

так це релізний код (з -d:release)? Нім додає для відладки багато коду.. Якщо компілити релізну версію — в ній дебажного коду небуде

Дійсно, пропустив реліз флаг. Тепер так:
nim js -d:nodejs -d:release --opt:size .\main.nim
Але все одно досить багатенько сгенерувало джаваскрипту, на 1164 рядки.
Source maps генерувати Nim ще не вміє для дебагу сгенерованого JS? Бачу лише відкритий пул реквест рік тому.
Жити можна, звичайно. Мова все одно цікава. Але, все ж таки, я б її радив розглядати не як альтернативу JS, а скоріш як альтернативу «системним» мовам (с/c++/objc таргети). А JS, на мій погляд, у Nim більше як бонусом іде. Та це лише імхо.

В не релізній версії повинні бути сорсмапи, для кожного рядка коду зберігається фрейм і нумерація.. ну якщо це те про що я подумав. Ну він мабуть і не альтернатива — просто можна код на німі зібрати під різні платформи. Ну і біндінги для різних js апі можна легко писати — як експортер анімацій з адоб афтерефект так і взаємодію з гугл докс апі. Так само Сішні/С++ ліби як SDL наприклад можна легко побіндити.

впечатлило, как в vs code можно дебажить js, когда работал с хаксом, для этих же целей приходилось дёргать хром.
и рад, что ребята потихоньку доросли до 4ки, а то у них последние года 3 очень мало мажорных апдейтов было, в основном, все последние тройки были про багфиксы и всякое такое. А хотя нет, не так давно был крупный хороший апдейт с фичами и пирогами, точно.
год работал с этим языком, игрухи пилили, понравилось. Такая себе смесь джавы и ас3, которая на выходе переваривалась в джаваскрипт. Жаль, на столичном рынке ультра-ограниченно востребован, хотя в том же Берлине, если не ошибаюсь, дефицит haxe-девов на серьёзные проекты

впечатлило, как в vs code можно дебажить js, когда работал с хаксом, для этих же целей приходилось дёргать хром.

О, там не только JS. В vscode завезли еще дебаггеры под HashLink и C++!

и рад, что ребята потихоньку доросли до 4ки

У них прибавилось хороших разработчиков в штате, так что сейчас взялись основательно и пилят активно. Из последнийх крутых фич: нулл-безопасность и JVM таргет. Да и HashLink за это время выкатили.
На 4.1, вроде как, тоже большие планы :)

а, да нет, это понятно было, что null safety полечили, хотел сказать, что вообще когда с обработкой и подходом к нулю в хаксе знакомился, был немного удивлён, ну хотя как бы я не был удивлён после джс и ас3, где такого в помине не было)
что-то вроде как когда узнал, что в питоне нету конструкции свитч... ну вроде мелочь, а вроде, ух ты, почему так, разбираешься с этим моментом и потом запоминаешь)

Автор этого бенчмарка протестировал Haxe, TypeScript, Dart и Wasm на одной и той же задаче.

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

Кроме того, заявочка, что Haxe лучший как язык (т.е. синтаксис/либы/фичи) очень и очень спорная, когда есть такие вещи как haskelljs и scala-js. Да и вообще, выбор широкий github.com/...​guages-that-compile-to-JS.

Хаскель, скала, ни тем более С++\С# и многие другие из списка не разрабатывались для того, чтобы быть транслированными в JS. То, что их транслируют — это натягивание совы на глобус и эзотерика применимая только в определенных случаях.

Haxe же изначально проектировался и затачивался под трансляцию в другие языки. К тому же хакс — это не только JS, а и десяток других таргетов. Именно отсюда выражение про лучший язык для фулл стэка: код легко переносится между платформами и разными языками. Пишешь на одном языке, а получаешь: нативные приложения через С++, веб через js и php, андроид через java и тд и тп.

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

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

не разрабатывались для того, чтобы быть транслированными в JS.

Тем не менее ScalaJS прекрасно работает с поправкой на отсутствие многопоточки. Но на самом деле побарабану если использовать cats.IO, и получается тот же самый код для фронтенда и для бекенда с одной и той же кодобазой. Но сама скала и сам хаскель как языки(т.е. синтаксис, система типов и либы) лучше вот этой императивной байды с ООП. Стрелки клейсли и IO монаду там точно так же красиво не напишешь и не поиспользуешь.

Давайте будем реалистами. JS разработчики не будут писать ни на скале, ни на хаскеле, потому что порог входа там намного выше. Это инструменты для скала и хаскель разработчиков. Как раз для них это оправданый инструмент, кто ж спорит.
Однако, JS разработчики пишут на TS, который — тот же ООП. Все что я делаю — предлагаю рассмотреть альтернативу.

К слову, в Haxe все является выражением — это из ФП. Сопоставление с образцом — это из ФП. Перечисляемые типы как форма алгебрагических типов данных — это из ФП.
Более того, благодаря макросам можно реализовать определенные синтаксические концепции из ФП.

JS разработчики не будут писать ни на скале

а им и не нужно, они с выхлопом ScalaJS будут работать из своего JS.

К слову, в Haxe все является выражением — это из ФП. Сопоставление с образцом — это из ФП. Перечисляемые типы как форма алгебрагических типов данных — это из ФП.

lambdaconf.us/...​ments/lambdaconf_slfp.pdf фп не заканчивается на том что вы написали.

а им и не нужно, они с выхлопом ScalaJS будут работать из своего JS.

Вы статью читали? Про хакс прочитали? Мне кажется, что нет. Либо просто издеваетесь.

фп не заканчивается на том что вы написали.

Спасибо, я в курсе. Но вы же сами окрестили хакс как ООП байду. Я указал на то, что там присутствует и идеи из ФП, от части...
Это перерастает в какой-то троллинг.
И я искренне не понимаю, что вы хотите тут доказать. То, что все не ФП языки говно? Супер, возьмите пирожок с полочки и поцепите себе медаль за победы на фронте срачей ФП vs ООП.
Мне же эти срачи не интересны.

Haxe же изначально проектировался и затачивался под трансляцию в другие языки

В чем это выражается?

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

В чем это выражается?

Собственно выражается в его идеи и концепции. Он изначально создан как язык, который будет преобразован во что-то другое.
Так же как TypeScript создавался для того, чтобы быть преобразованным в JS. Просто у Haxe намного больше поддерживаемых платформ.

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

Другими словами, это не просто конвертер из одного языка в другой.

First appeared 2005; 14 years ago и все еще нету cdn.sstatic.net/...​uage-1.svg?v=d63c4a852014
В отличии от трех конкурентов из бенчмарка

Как вам удалось его завезти на проект(или это не прод)?

В свое время, Google реджектил все заявки от Haxe Foundation на свои JS события. Совпадение? Не знаю, но очевидно, что не хотел конкуреции с Dart.
У хакса нет MS или Google за спиной, потому продвигать язык самостоятельно им было сложно. Таким образом он остался где-то в тени. Но это не делает его плохим инструментом. Скорее даже наоборот: то, что он очень активно развивается спустя столько лет — только подтверждает его зрелость и серьезность.
Собственно, я потому и пишу статьи, чтобы его популяризировать.

Как вам удалось его завезти на проект(или это не прод)?

Прод и не один :) Язык — это инструмент. JS разработчику писать на Haxe не сложнее чем на TS. У вас без проблем могут писать часть кода на JS, часть на Haxe — технически это не вызывает каких-то проблем.
Можно даже сказать, что я не особо вижу смысла в вакансиях типа «Haxe разработчик». Потому что если вы пишете веб, то вам нужен именно JS разработчик, а не Haxe. На хаксе он сможет писать и так без проблем. Решающий фактор — это ведь не знания языка, а скорее знания платформы.
Так что не вижу преград с его интеграцией. Преграда может быть разве что в некомпетентном руководстве.

---
Haxe очень активно используется на проде в играх. Так сложилось исторически. Многие флешеры перешли на него.
Тот же agar.io написан на OpenFL + Haxe;
Papers, Please \ Dead Cells \ Evoland \ Northgard — это все тоже на Haxe, игры с большой аудиторией, десктопные платформы, консоли. Список действительно может быть большим, я назвал только то, что вспомнил.

Используют его и в Slack для некоторых внутренних тулзов.
Используют в известном 18+ стримминговом вебкам сервисе с милионной аудиторией и тд итп.

язык Haxe

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

Нужно разработать систему плагинов и скоращённо называть это HAXEP

Жаль, англоязычная аудитория шутку не оценит.
Но доля лулзов в названии есть, что поделать, не отрицаю :)

Знову чергова ООП надбудова над скриптовою мовою... Фу...

Наскільки я пам’ятаю історію haxe, то він створювався для того, щоб можна було писати код однією мовою, а потім компілювати його в js, flash, php. І було це досить давно.
Тому я б не називав його «черговою надбудовою».

Байдуже які були передумови створенню мови. Кока-кола теж була лікувальним засобом проти нервових розладів. Важливо, яка парадігма в неї зараз. А це ООП.

Саме так. Окрім JS, Haxe дає можливість писати добре оптимізований платформонезалежний код під десяток мов, без будь-яких VM.
Також, я писав, що він один із перших, якщо не перший, хто почав компілюватись в JS (2006 рік), тому назвати його «черговим» — м’яко кажучи некоректно.

Вибачте, то ви класи і ото всьо не використовуєте у сучасному стандарті, я сподіваюсь? :)

Класи? Які-такі класи? o_O

Подскажите, поддержка JSX реализована в компиляторе? В планах? Иначе TS не победить

На уровне синтаксиса была добавлена поддержка разметки (в 4 версии), так называемых markup литералов: var v = <test/>. Которые могут быть распарсены в любой необходимый синтаксис макросами.

Из готовых решений есть, например, вот такое: github.com/haxetink/tink_hxx

Спасибо. Нужно будет попробовать. А есть поддержка генерации декораторов, или в haxe это достигается другими способами?

В хаксе это можно сделать, но на уровне типов через статические расширения: haxe.org/...​/lf-static-extension.html

Декорирования функций как у TS из коробки нет, но оно однозначно может быть реализовано через макрос. К сожалению, готовых решений не встречал.

Підписатись на коментарі