Надысь побаловался Elm'ом - хаскелеподобным языком, компилирующимся в JavaScript (не писать же руками на этом странном языке). Вспомнил, где можно его применить - у меня на странице одного из продуктов было слайд-шоу, наскоро сделанное на флеше с использованием Flex'a, отчего простая довольно флешка весила 280 КБ. Исходников той флешки сейчас под рукой нет, видать остались на старом ноуте, так что сравнить объем не могу, но на Elm'e все уместилось в несколько строк, широко размазанных для пущей вящести. Вот они, с пояснениями.
( Read more... )
Вот и весь код. На самом деле, все описания типов тут можно смело удалить, компилятор неплохо справляется с их выводом и сгенерит ту же самую программу без подсказок. Типы тут чисто для нас. Натравив конпелятор на этот код, я получил 3 КБ JavaScript'a с логикой, и ссылку на elm-runtime.js, где лежит весь-весь рантайм (включая массу неиспользуемых модулей вроде работы с Canvas и WebSockets) непожатый - около 160 КБ. Его можно closure compiler'ом ужать до 80 в базовом режиме. В продвинутом режиме, с удалением неиспользуемого кода, у меня его не получилось использовать - что-то в результате ломалось, да и выигрыш был небольшим (несколько процентов). В итоге оставил вариант с 83 КБ слегка пожатого JS, что для такой задачи все равно очень много, но все же на 200 КБ лучше, чем раньше с Flex'ом.
Работает Elm'овское FRP дискретно и push-based. Программа превращается в направленный ациклический граф, где источники сигналов, вроде every t или событий мыши, клавиатуры, окна, полей ввода и т.д. становятся вершинами-источниками (у них только исходящае дуги), а функции, преобразующие сигналы, становятся промежуточными вершинами (с входящими и выходящими дугами), main становится вершиной-приемником. Каждый раз, когда в одном из источников происходит какое-то событие (нажатие или перемещение мыши, тик таймера, нажатие кнопок и т.п.), все вершины источники генерируют по сообщению. Только там, где реально произошло событие, это сообщение с новым значением, а во всех остальных - сообщение "ничего не изменилось, старое значение такое-то". Когда в промежуточную вершину с N входами приходит N сообщений по входящим дугам, вершина проверяет, есть ли среди них новая информация или все "без изменений". Если есть новая, то вершина-функция пересчитывает свое значение с учетом новых аргументов, а иначе просто посылает вниз "без изменений", ничего не пересчитывая. В результате все работает синхронно, дискретно, и без лишних пересчетов. Есть также возможность делать нужные части графа асинхронными, чтобы долгие вычисления не замораживали весь граф (подробности могу описать отдельно, или см. доки).
Теперь о минусах. Их пока довольно много. Язык хоть и напоминает хаскель, на самом деле очень далек от него по возможностям и синтаксису. Это слегка приукрашенная просто типизированная лямбда. Есть let, но нет where. Нет do (впрочем, для него нет и повода). Паттерн-матчинг только по алгебраическим типам, простые типы не матчатся (если я правильно путаю). Есть пара псевдо-тайпклассов "Number a" и "Comparable a", но свои тайпклассы объявлять нельзя, никаких монад вам. Компилятор очень ненадежен: часто не ловит банальнейшие ошибки (вроде неизвестного идентификатора) и молча генерит код, который потом вылетает с рантайм-ошибками в браузере. Стандартная библиотека бедновата, особенно по возможным входящим сигналам - на многие события пока нельзя реагировать (например, я бы хотел реагировать на загрузку картинки - сейчас это недоступно). Документация разрозненная и неполная. Вот thesis стоит почитать, хотя бы ради обзора более ранних чужих подходов к FRP.
( Read more... )
Вот и весь код. На самом деле, все описания типов тут можно смело удалить, компилятор неплохо справляется с их выводом и сгенерит ту же самую программу без подсказок. Типы тут чисто для нас. Натравив конпелятор на этот код, я получил 3 КБ JavaScript'a с логикой, и ссылку на elm-runtime.js, где лежит весь-весь рантайм (включая массу неиспользуемых модулей вроде работы с Canvas и WebSockets) непожатый - около 160 КБ. Его можно closure compiler'ом ужать до 80 в базовом режиме. В продвинутом режиме, с удалением неиспользуемого кода, у меня его не получилось использовать - что-то в результате ломалось, да и выигрыш был небольшим (несколько процентов). В итоге оставил вариант с 83 КБ слегка пожатого JS, что для такой задачи все равно очень много, но все же на 200 КБ лучше, чем раньше с Flex'ом.
Работает Elm'овское FRP дискретно и push-based. Программа превращается в направленный ациклический граф, где источники сигналов, вроде every t или событий мыши, клавиатуры, окна, полей ввода и т.д. становятся вершинами-источниками (у них только исходящае дуги), а функции, преобразующие сигналы, становятся промежуточными вершинами (с входящими и выходящими дугами), main становится вершиной-приемником. Каждый раз, когда в одном из источников происходит какое-то событие (нажатие или перемещение мыши, тик таймера, нажатие кнопок и т.п.), все вершины источники генерируют по сообщению. Только там, где реально произошло событие, это сообщение с новым значением, а во всех остальных - сообщение "ничего не изменилось, старое значение такое-то". Когда в промежуточную вершину с N входами приходит N сообщений по входящим дугам, вершина проверяет, есть ли среди них новая информация или все "без изменений". Если есть новая, то вершина-функция пересчитывает свое значение с учетом новых аргументов, а иначе просто посылает вниз "без изменений", ничего не пересчитывая. В результате все работает синхронно, дискретно, и без лишних пересчетов. Есть также возможность делать нужные части графа асинхронными, чтобы долгие вычисления не замораживали весь граф (подробности могу описать отдельно, или см. доки).
Теперь о минусах. Их пока довольно много. Язык хоть и напоминает хаскель, на самом деле очень далек от него по возможностям и синтаксису. Это слегка приукрашенная просто типизированная лямбда. Есть let, но нет where. Нет do (впрочем, для него нет и повода). Паттерн-матчинг только по алгебраическим типам, простые типы не матчатся (если я правильно путаю). Есть пара псевдо-тайпклассов "Number a" и "Comparable a", но свои тайпклассы объявлять нельзя, никаких монад вам. Компилятор очень ненадежен: часто не ловит банальнейшие ошибки (вроде неизвестного идентификатора) и молча генерит код, который потом вылетает с рантайм-ошибками в браузере. Стандартная библиотека бедновата, особенно по возможным входящим сигналам - на многие события пока нельзя реагировать (например, я бы хотел реагировать на загрузку картинки - сейчас это недоступно). Документация разрозненная и неполная. Вот thesis стоит почитать, хотя бы ради обзора более ранних чужих подходов к FRP.