Andrey Prokopenko's Blog

Yesod + Fay

Проблема

Во время разработки front-end вообще и использования фреймворка Yesod в частности приходится иметь дело с JavaScript. Разумеется, пока. Дело в том, что для работы с JS рекомендуется использовать Shakespearean Template. А значит, куски JS будут лежать либо в файлах с расширением .julius, либо в Template Haskell. Проблема удобства разработки встаёт тут же. Yesod предоставляет живую перезагрузку кода. Тем самым, цикл разработки становится следующим:

  • написание кода;
  • сохранение изменений;
  • автоматическая перекомпиляция изменений (если изменялись шаблоны hamlet или обработчики);
  • создание нового js файла по адресу static/tmp/autogen-*.js;
  • перезагрузка страницы в браузере;
  • тестирование изменений.

А значит, если у нас в коде ошибка, то мы отловим её в runtime. Вдобавок, сказывается отсутствие REPL для JS. Если поднять cabal repl, то из ghci нереально будет проверить JS-код.

Что же делать? Как сместить баги из runtime в compile-time? Как добавить в ужас JS немного статической типизации?

Способы решения

На эти вопросы уже есть ответы.

GHCJS, Elm - судя по отзывам, превосходные вещи. Однако мне не довелось ещё соприкоснуться с ними. У Сноймана были какие-то мысли на их счёт, но я не вдавался в подробности и не могу ничего сказать на сей счёт конкретного.

Fay

Fay - это подмножество Haskell, которое можно успешно скомпилировать в валидный JS код. Среди возможностей Fay отмечают:

  • статическая типизация;
  • лень;
  • чистота;
  • базовые типы, с которыми коррелирует JS + алгебраические типы данных;
  • автоматическое преобразование данных из/в JSON.

Как можно заметить из их wiki, он успешно интегрирован во многие вэб-фреймворки. Но остановимся на Yesod.

Yesod + Fay

Создадим обычный проект на yesod:

Выберем pf

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

  • во-первых, сама СУБД: postgresql-9.4
  • во-вторых, нужные для работы пакетов библиотеки: libpq-dev;
  • в-третьих, нужно сконфигурировать юзера postgres в СУБД, если вы этого ещё не сделали ранее.

После того, как зависимости разрешены, создаём базу данных и юзера.

Подсоединяемся к СУБД.

Создаём базу и юзера.

Теперь заводим проект. Как поднимется, смотрим тут.

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

В структуре проекта появились следующие файлы:

В шаблоне templates/homepage.hamlet находим строки:

Текстовое поле для ввода и вывода значений. Где же описана логика?

В обработчике Handler/Home.hs находим строку $(fayFile "Home"). Так подключается fay/Home.hs.

Посмотрим на содержимое:

С помощью монады Fay осуществляются все преобразования с побочными эффектами, прямо как с IO. Не правда ли, код стал более читаем? Часть функций мы импортировали из DOM, call - из Fay.Yesod, остальное - из FFIExample. Как можно заметить, у нас тут и клиентская, и серверная часть. Как водится, они разделены. С помощью call мы делаем асинхронную отправку запроса по адресу /fay-command. Сервер принимает запрос и вызывает обработчик appFayCommandHandler. В Application.hs прописывается функция onCommand из Handler.Fay, которая отрисовывает на клиенте число Фибоначчи по индексу.

Для того, чтобы разобраться с тем, как в Fay реализована клиентская и серверная часть, рассмотрим их по отдельности.

Клиентская часть

Все файлы, относящиеся к клиентской части лежат в директории fay:

В Home.hs лежит подключаемый fayFile. Его содержимое мы уже рассмотрели выше.

В Fay/Yesod.hs лежат функции для взаимодействия с сервером. Ровно то же самое можно обнаружить в хакадже.

В FFIExample - самое интересное: примеры реализации внешнего интерфейса вызова JS. С побочными действиями, со всеми делами.

Как можно заметить, мы не передаём аргументы в функцию, а используем порядковые номера %1, %2 и так далее, как в Си.

Серверная часть

Все файлы, относящиеся к серверной части, лежат в fay-shared. В нашем случае это SharedTypes.hs:

Command здесь - это тип, который принимает сервер и обрабатывает в Handler.Fay (напомню, что обработчик можно найти по адресу Handler/Fay.hs). Выше уже это упомяналось, рассмотрим же теперь подробнее:

Нетрудно заметить, что при добавлении новых конструкторов типов, можно будет сопоставлением с образцом легко дополнить взаимодействие с сервером так, как того требует бизнес-логика приложения. Да, это устаревающий AJAX, но он ещё не совсем умер. С этим действительно можно работать.

Fay Runtime

А как это чудо выглядит после компиляции? Помните, в начале в списке файлов проекта появились static/fay-runtime.js, static/faygen-*.js? Вот это как раз оно и есть. В файле fay-runtime.js содержится Prelude и множество полезных вещей, которые мы можем использовать. В сгенерированном файле содержатся результаты компиляции файлов из директории fay.


Posted on 2015-10-03 by agr . Powered by Hakyll. Inspired by Yann Esposito.