[Ненормальное программирование, Разработка веб-сайтов, Программирование, Haskell] Зачем мы транспилируем Haskell в JavaScript

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
23-Мар-2021 19:31

Зачем нужна транспиляция в JavaScript
Привет, Хабр! Сегодня мы расскажем, почему мы пишем фронтенд на Haskell и компилируем его в JavaScript. Вообще говоря, подобный процесс называется транспиляцией:
Транспиляция — это процесс преобразования программы на языке X в эквивалентную программу на языке Y. В отличие от компиляции, языки X и Y находятся примерно на одном и том же уровне абстракции.

Зачем нужна транспиляция?
В общем случае можно выделить две основные цели транспиляции:
  • Миграция между разными версиями одного языка. Языки программирования не стоят на месте, активно развиваются и обрастают новыми удобными фичами с каждой новой версией, которые хочется использовать. К сожалению, везде и сразу новые средства языка могут не поддержаваться, поэтому возникает вопрос об обратной совместимости версий. В данном случае такой межверсионный транспилятор производит что-то вроде "рассахаривания" (deshugaring) конструкций в более старые и обычно менее выразительные версии. Примером может служить Babel, переводящий код на JS в его подмножество, поддерживаемое браузерами. Возможно и преобразование в другую сторону, когда необходимо перевести проект на более новую версию языка, а делать вручную это долго и лень. Например, для транспиляции кода на Python 2.x в код на Python 3 есть 2to3.
  • Перевод с одного ЯП на другой, исходя из требований рантайм системы и/или пожеланий разработчиков. Например, для исполнения в браузере требуется код на JS (чаще всего применяется на данный момент) или WASM (пока что менее распространён), а для разработки ставятся требования, которым лучше соответствует другой язык. Этот исходный язык может поддерживать уникальные механизмы, такие как автоматическое распараллеливание, или же вообще относиться к другой парадигме. Код, генерируемый транспайлерами, может быть как максимально похож на исходный (это упрощает отладку), так и стать неузнаваемым по сравнению с кодом на исходном языке. Существуют утилиты, позволяющие сопоставить резуьтат транспиляции с оригинальным кодом (например, SourceMap для JS).

Приведём несколько примеров:
  • Языки для фротенд-разработки, транслируются в JS:
        — TypeScript — надмножество JavaScript с опциональными аннотациями типов, которые проверяются во время транспиляции.
        — CoffeeScript — более выразительный по сравнению с JS язык, в который добавлен синтаксический сахар в духе Python и Haskell.
        — Elm — чисто функциональный язык со статической типизацией (и в целом похожий на Haskell), позволяющий создавать веб-приложения в декларативном стиле, который так и называется The Elm Architecture (TEA). 
        — PureScript — тоже чисто функциональный и статически типизированный язык с Haskell-подобным синтаксисом.
        — ClojureScript — расширение языка Clojure (который, в свою очередь, диалект Лиспа) для веб-программирования на стороне клиента.
  • Языки описания аппаратуры:
        — Bluespec — высокоуровневый функциональный язык описания аппаратуры, изначально был расширением Haskell, транспилируется в Verilog.
        — Clash — также функциональный, с похожим на Haskell синтаксисом, генерирует код на VHDL, Verilog или SystemVerilog.
        — Verilator — в отличе от предыдущих двух, работает в другую сторону и преобразует подмножество Verilog в C++ или SystemC.
  • Транспиляторы языков ассемблера для различных архитектур или под разные процессоры из одной системы архитектур (например, между 16-битным Intel 8086 и 8-битным Intel 8080).

Почему бы не вести разработку на чистом JS?
Как можно увидеть из приведённых выше примеров, разговор о транспиляции в целом неизбежно затрагивает трансляцию в JS. Давайте разберём более подробно, какие цели это преследует и какие может дать преимущества:
  • Транспиляция в JS позволяет запустить приложение в веб-браузерах.
  • Разработчики используют те же самые инструменты, что и для разработки бэкенда, поэтому не нужно изучать другие инфраструктуры библиотек, менеджеры пакетов, линтеры и т.п.
  • Появляется возможность использовать ЯП, который ближе отвечает предпочтениям команды и требованиям проекта и получить чужеродные консервативному фронтенд-стеку механизмы, такие как строгая статическая типизация.
  • Общую для фронтенда и бэкенда логику можно вынести отдельно и переиспользовать этот код. Например, подсчёт общей стоимости заказа может быть нетривиальным из-за специфики предметной области. На клиенте нужно отобразить стоимость заказа, а во время обработки запроса на сервере нужно всё заново перепроверить и пересчитать. Саму бизнес-логику подсчёта общей стоимости заказа можно написать один раз на одном языке и использовать в обоих местах.
  • Используются механизмы кодогенерации и генерики, которые, например позволяют убедиться что сериализация и десериализация в JSON или даже бинарное представление будет работать без проблем. Мы использовали такой подход для ускорения разбора запросов, приводящих к большому объему парсинга, чем смогли в ряде случаев, улучшить производительность.
  • Упрощается процесс отслеживания совместимости API между клиентом и сервером. При синхронной раскладке клиенсткого и серверного приложений, а также правильной работе с кэшами в браузерах, должны отсутствовать ситуации с несовместимостью, которые возможны при асинхронных выкладках. Например, если одна часть приложения обращается к другой по API, и API изменяется, есть шанс забыть об этих изменениях на клиенте и потерять какой-нибудь параметр запроса или отправлять тело запроса в неправильном формате. Этого можно избежать, если клиентское приложение написано на том же языке. В идеале оно даже не пройдёт компиляцию, если клиентская функция не соответствует текущей версии API.
  • Разработчики одной квалификации участвуют и в бэкенд, и во фронтенд задачах, что дает дополнительную организационную гибкость для команд и увеличивает автобусный фактор. Так становится проще распределять задачи и нагрузку на каждого из членов команды. Это важно и когда нужен срочный фикс — самый "незагруженный" берёт задачу независимо от того, к какой части проекта она относится. Один и тот же человек может исправить и валидацию поля на фронтенде, и запрос к БД, и логику хендлера на сервере.

Наш опыт транспиляции в JS
При выборе инструментов для фронтенд-разработки мы принимали во внимание следующие факторы:
  • Хотелось использовать язык со строгой статической типизацией.
  • У нас уже существовала достаточно объёмная кодобаза для бэкенда на Haskell.
  • Большинство наших сотрудников имеет серьёзный опыт промышленной разработки на Haskell.
  • Мы хотели воспользоваться преимуществами одного стека.

На данный момент мы в Typeable ведём фронтенд-разработку на Haskell и используем веб-фрейморк Reflex и функциональное реактивное программирование (FRP). Исходный код на Haskell транспилируется в код на JavaScript с помощью GHCJS.
TypeScript и прочие расширения JS нам не подошли из-за недостаточно строгой типизации, не такой развитой системы типов, как в Haskell, да и в целом эти языки слишком радикально отличаются от привычных для нашей команды.
Reflex мы предпочли таким вариантам как Elm и PureScript в первую очередь из-за желания использовать тот же стек разработки, что и для бэкенда. Кроме того, Reflex позволяет не следовать определённой архитектуре приложений и в какой-то степени является более гибким и "низкоуровневым". Подробнее про сравнение Elm и Reflex можно прочитать в нашем посте на эту тему.
Выводы
Нам удалось получить те преимущества транспиляции в JS, о которых мы рассказали выше:
  • Разработка всех частей проекта ведётся с использованием одного стека, а участники команды являются "универсальными" программистами.
  • Упрощённо структура проекта представляет собой несколько пакетов: описание API, описание бизнес-логики, бэкенд и фронтенд. Первые два из них являются общими частями для фронтенда и бэкенда, значительная часть кода переиспользуется.
  • Мы используем библиотеку servant, которая позволяет описать API на уровне типов и проверить во время компиляции, что, как обработчики на сервере, так и функции для отправки запросов на клиенте, используют правильные параметры нужных типов и соответствуют актуальной версии API (забыли поменять клиентскую функцию на фронденте — он просто не соберётся).
  • Функции для сериализации и десериализации в JSON, CSV, бинарное представление и т.п. генерируются автоматически и одинаково на бекенде и фронтенде. Про API слой можно практически не думать.

Разумеется, есть и определённые трудности:
  • Всё ещё приходится использовать вставки на чистом JS для работы с внешними плагинами.
  • Отладка становится сложнее, особенно пошаговый режим. Однако такое требуется крайне редко, большинство ошибок оказывается в логике реализации.
  • Меньшее количество документации по сравнению с фреймворками на JS.

Соавтор: Катерина Галкина
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_nenormalnoe_programmirovanie (Ненормальное программирование), #_razrabotka_vebsajtov (Разработка веб-сайтов), #_programmirovanie (Программирование), #_haskell, #_haskell, #_javascript, #_transpilation, #_crosscompile, #_crosscompiling, #_webrazrabotka (web-разработка), #_webprogrammirovanie (web-программирование), #_blog_kompanii_typeable (
Блог компании Typeable
)
, #_nenormalnoe_programmirovanie (
Ненормальное программирование
)
, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_programmirovanie (
Программирование
)
, #_haskell
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 18-Май 11:20
Часовой пояс: UTC + 5