Pull to refresh

Comments 13

UFO just landed and posted this here

Я думаю вам стоит ознакомиться с разделом "Зачем нужна транспиляция в JavaScript". Его, конечно, очень легко было не заметить

Такой подход имеет какой-то процент покрытия по сравнению с чистым JS?
Манипуляция с DOM, работа с файлами, drag&drop, какие-то специфичные js библиотеки.
Ситуации, когда проще написать на js. Ситуация, когда сторонний проект не с нуля и там js.
Это очень здоровский ход(конём): «Упрощается процесс отслеживания совместимости API между клиентом и сервером». Сам столкнулся с подобным, используются вычисления как на клиенте, так и на сервере, в итоге нужно изменения применять в 2х местах. А данный подход мог бы упростить работу.
Если я правильно понял вопрос про покрытие, то все основное уже обтянуто биндингами, а даже если и чего-то нет, то можно сделать тонкую обёртку через JS FFI в GHCJS. В целом нам не приходилось этого делать ни для чего вышеупомянутого — только для подключения библиотек и внешних компонентов. outline.js, например, в каждом первом проекте используется.
Можно было упомянуть F# и Fable — его компилятор в JavaScript. Целый стек для разработки на них: SAFE, Elmish и новоиспеченный Sutil (аналог Svelte). Бандлы JS получаются вполне приемлемых размеров и даже остаются читаемы, есть приличный FFI
К плюсам транспиляции из статически-типизированных языков можно добавить гораздо большие возможности compile-time оптимизаций, в том числе dead code elimination.
Среди примеров можно было бы упомянуть Reason и Kotlin.
Отдельного внимания заслуживает haxe, который тоже обладает полноценной системы типов, хорошим апи для метапрограммирования, но транспилируется не только в js, а еще и в C++, C#, Java, JVM, Python, Lua, PHP, SWF.
пилили на HaXe игрухи, которые потом переваривали в JS ES5, было круто, но дебаг и оптимизация производительности периодически были крайне непростыми. В зависимости от происходящего твой класс на HaXe в 20 строк могло переварить в 50 — 300 строк кода на джаваскрипте-пятёрке и когда после языка высокого уровня нужно было лазить в прототипно-ориентированной каше, разбираясь, что где течёт или много жрёт — иногда морочились днями-неделями, пока полировали игру перед релизом. Ещё нравилась сильная система кастования, возможность делать дженерики и отдельно запомнилось, что у языка 2 null — для скриптовых языков и для статически-типизируемых и динамически-типизируемых языков. Жаль, что язык представлен в разработках очень нишево, хотя всё-таки поддерживать этот слоёный пирог — нетривиальная задача
пилили на HaXe игрухи, которые потом переваривали в JS ES5, было круто, но дебаг и оптимизация производительности периодически были крайне непростыми. В зависимости от происходящего твой класс на HaXe в 20 срок могло переварить в 50 — 300 строк кода на джаваскрипте-пятёрке

И обратное тоже возможно: благодаря dce из 100 строк получить 20.
и когда после языка высокого уровня нужно было лазить в прототипно-ориентированной каше,

Как раз для js там очень прозрачные трансформации, структурно код очень близкий, просто блок может назначаться через свойство prototype. Основное отличие, которое бросалось в глаза – это for трансформирующийся в while с вынесением объявления счетчика, мне кажется это могло быть основной причиной раздувания.
ES6 на классах сейчас тоже поддерживается через флаг компиляции.
разбираясь, что где течёт или много жрёт — иногда морочились днями-неделями, пока полировали игру перед релизом.

Если искать протечки в своем коде, а не в движке, то обилие таргетов может помочь. Искать безудержно аллоцирующие места можно на любой платформе, выбирая по наличию удобных инструментов. Flash в этом весьма хорош, кстати. Быстрая компиляция, быстрый запуск, отличный дебаггер. HxScout есть, работает и для swf, и для c++.
Ещё нравилась сильная система кастования, возможность делать дженерики

Дженериками сегодня никого не удивишь, ADT это уже интереснее, а аналогов абстрактам в популярных языках я и не назову.
и отдельно запомнилось, что у языка 2 null — для скриптовых языков и для статически-типизируемых и динамически-типизируемых языков.

Я бы сказал немного по-другому: haxe старается не вносить оверхэд на ровном месте, поэтому null на haxe превращается в null на целевом языке, а вот его поведение уже определяется природой целевой платформы.
Жаль, что язык представлен в разработках очень нишево

Вот с этим соглашусь, пиарить его особо не кому, кто знает – тихо использует. Кто не знает – думает, «если он такой хороший, что ж я про него не знал до сих пор» и проходит мимо. Поэтому сложнее набирать команду – это риск для бизнеса, замкнутый круг.
а аналогов абстрактам в популярных языках я и не назову.

Newtype в haskell.


Наследники AnyVal в scala, но есть ограничения. При определенных условиях значение все же может быть завернуто в класс.

Не имел дел на практике с обоими языками, так что могу быть не точным. Все же разница есть, и может быть существенной ввиду двух факторов:
1. haxe – мультипарадигменный язык с уклоном в ООП, большая часть прикладного кода пишется в императивном ООП-стиле
2. haxe – мультитаргетный транспилятор, одна из широко используемых возможностей – шаринг кодовой базы между целевыми платформами. Написание прикладного кода так, чтобы он работал на всех (нужных) платформах одинаково и эффективно – задача специфическая, решать ее не всегда требуется. Но если все же – то аналогов я не знаю.
Сейчас попробую пояснить. Действительно, один из способов применения абстрактов в haxe – zero-overhead описание алгебры предметной области. То есть, можно описать, что метры*метры = метры квадратные, складывать их между собой можно, а друг с другом – нельзя.
Scala – пожалуй, единственный язык из известных мне, который имеет похожую абстракцию и позволяет писать в императивном стиле. И то – вопрос еще, что получится при транспиляции в js. Как минимум, раньше она тащила с собой жирнющий рантайм в несколько мегабайт.
А при транспиляции хаскеля в js newtype превращается просто в underlying type?
Как бы то ни было, абстракты в хакси могут быть над любым типом, а не только примитивным, у них явно описывается конструктор с произвольным телом. Кроме этого, можно явно управлять способом транспиляции для каждого метода или перегруженного оператора: если в объявлении не указан инлайн, то сгенерируется хэлпер со статическим методом.
У Haxe есть экстерны для описания типов целевой платформы, абстракты – это отличный способ изолировать особенности «нативных» сущностей с разных платформ, добавить к ним некоторое поведение, не внося новых сущностей в целевой код.
То есть в некотором смысле абстракты еще могут быть и альтернативой миксинам/трейтам.
Хороший пример – реализация сигналов. Абстракт над массивом, этакий аналог (почти) invokation list в пару строк и без внесения лишних сущностей в язык.
Решил приложить пример в качестве дополнения, там сразу можно посмотреть сгенерированный js. Такому вот есть аналоги?

Есть, но кодогенерация оставляет желать лучшего. В Scala аналог выглядит так:


import scala.scalajs.js.Array

object types {
  type Handler[T] = T => Unit

  class Signal[T](val handlers: Array[Handler[T]]) extends AnyVal {
    def +=(h: Handler[T]): Unit = handlers += h

    def dispatch(t: T): Unit = handlers.foreach(_(t))
  }

  object Signal {
    def apply[T](): Signal[T] = new Signal[T](Array[Handler[T]]())
  }
}

object Main {
  def main(args: scala.Array[String]): Unit = {
    val s = types.Signal[String]()

    s += { s => println(s) }

    s.dispatch("Hello")
  }
}

Параметр handlers у класса-обертки должен быть val т.е. неизменяемый. Но в данном случае это ссылка, сам то объект по ссылке может быть мутабельным.
Родной scala/java массив, который мог бы быть транслирован в js массив, использовать нельзя. Потому что в jvm массивы размеры динамически менять не умеют, а выделить новый массив и поменять ссылку мы не можем, потому что ссылка константная.


Если использовать коллекцию какую-нибудь из стандартной библиотеки, тогда ее реализация сгенерируется в js коде. У меня получилось 11к строк. Если же использовать прокси тип к нативном js массиву (как в моем примере), тогда уже 2к строк. Но в таком случае код уже не совместим в jvm.


На самом деле это не проблема, Scala поощряет использование неизменяемых типов, и +=следовало бы заменить на + который бы возвращал новый объект Signal, но я ваш код повторял.


Нагенерировалось вкратце это:


$c_Lcom_habr_Main$.prototype.main__AT__V = (function(args) {
  var s = $m_Lcom_habr_types$Signal$().apply__sjs_js_Array();
  $m_Lcom_habr_types$Signal$().$plus$eq$extension__sjs_js_Array__F1__V(s, new $c_sjsr_AnonFunction1(((this$1) => ((s$2$2) => {
    var s$2 = $as_T(s$2$2);
    var this$3 = $m_s_Console$();
    var this$4 = this$3.out__Ljava_io_PrintStream();
    this$4.java$lang$JSConsoleBasedPrintStream$$printString__T__V((s$2 + "\n"))
  }))(this)));
  $m_Lcom_habr_types$Signal$().dispatch$extension__sjs_js_Array__O__V(s, "Hello")
});
...
$c_Lcom_habr_types$Signal$.prototype.apply__sjs_js_Array = (function() {
  return []
});

Последняя строчка это конструктор, который на самом деле возвращает голый массив. var s инициализируются массивом, дальше работа идет с голым массивом, про Signal есть лишь упоминание в именах функций.


Если не использовать прокси класс к js массиву, то Signal обертки все равно не будет. Просто вместо голого массива будет использоваться выбранная коллекция.


Учтите, что я ни на scalajs и на js никогда не писал, это в первый раз. Возможно транслятор настройки какие имеет. Глазами я конечно не хотел бы это читать, но source map он генерирует. Но у haxe и в правду js код получился хороший.

Спасибо за развернутый пример. Действительно, почти аналог, хоть и с некоторым оверхедом.
Если по-честному, то js-таргет у haxe немного в более привилегированном положении, так как его Sdt (в т.ч. стандартный массив) схож с js-ными по апи, так как исторически был альтернативой as3, который тоже является ECMAScript. Для других таргетов код массива тоже будет включен в сгенерированный.

Родной scala/java массив, который мог бы быть транслирован в js массив, использовать нельзя. Потому что в jvm массивы размеры динамически менять не умеют, а выделить новый массив и поменять ссылку мы не можем, потому что ссылка константная.

Но прелесть haxe как мультитранспилера выражается в том, что мы можем написать
typedef Signal<T> = #if (java||jvm) AbstractOnJLinkedList #else AbstractOnArray #end

а код в абстрактах для подгонки апи будет инлайниться.
+=следовало бы заменить на + который бы возвращал новый объект Signal

Ненене. Haxe изначально очень практичный и прикладной инструмент, а массовый прикладной код сейчас все-таки пишется императивный-ООП-стайл, и я заходил именно с этой стороны. То есть, ожидается, что я могу отдать куда-то ссылку на сигнал, потом в него что-то напихать, а кто-то по ссылке до напиханного достучится.
Хотя в то же время на haxe можно написать и OCaml-стайл портянку паттернматчина по AST, представленного в виде ADT. Собственно, macro апи для метапрограммирования тоже является отличительной фишкой haxe.
Sign up to leave a comment.