Haskell для ВКонтакте, JavaScript и ReactJS, Или «Чужой против Симпсонов»
Данный пост является попыткой добавить пару капель топлива в машину пропаганды Haskell, демонстрируя его использование в повседневных задачах.
В качестве таковых рассмотрим следующие:
- Реализуем пакет доступа к API ВКонтакте. Код будет работать как в «native» приложениях, так и в приложениях JavaScript через GHCJS, компилятор Haskell в JavaScript
- Напишем одностраничное браузерное приложение, используя наше API
API Вконтакте
Полный код пакета приведен здесь vk-api.
Типичное использование разрабатываемого нами API будет выглядеть следующим образом
Основой для реализации API ВКонтакте послужит пакет api-builder.
Запросы и результаты мы хотим представлять в виде записей ADT . Ответы будем получать в виде JSON.
Описание операций APIНа уровне типов свяжем запросы и результаты через класс Routable
Декларируя функциональную зависимость q -> a, мы клятвенно обещаем компилятору, что отображение типов запросов на типы результатов будет однозначно.
Конечное описание каждой операции API будет емкое и удобочитаемое, например для audio.getLyrics
Описание запросовТип запроса должен быть экземпляром класса Queryable для конвертации в список url-параметров
Реализация каждого конкретного экземпляра Queryable — дело легкое, но нудное, потому создадим макрос Template Haskell, пусть компилятор трудится за нас, а мы хотим затратить минимум усилий на описание наших запросов.
Haskell в отношении макрологии далеко не Lisp, но в создании базового шаблона нам поможет интерпретатор. Попросим его показать AST для желаемого выражения.
Далее остается находить по именам полученного AST соответсвующие функции в Language.Haskell.TH и конструировать наш макрос deriveQueryable.
Функции Haskell не имеют опциональных параметров, но мы предусмотрим значения по-умолчанию, описав для запросов экземпляры класса Default.
Пользователь сможет изменять только интересующие его атрибуты записи.
Описание ответовСвязь JSON ответов с записями ADT для каждого типа результата будет определена экземпляром класса Receivable.
С автоматизацией конвертирования JSON в записи ADT легко справляется aeson.
Прерывание последовательности вычисленийИспользование типов Maybe,Either в монадическом контексте или монадных трансформеров MaybeT, EitherT, ExceptT и.т.д позволяет прервать вычисление на первом «исключении», избегая утомительных проверок.
Haskell в данном подходе не одинок, так опциональные последовательности в Swift являются не чем иным, как монадой Maybe «для бедных», впиленной на уровне синтаксиса.
Пакет errors предоставляет всевозможные функции перепинывания вычислений«взад-назад» между членами этой команды. Чем мы и воспользуемся, иначе наш код загрузки аудиофайлов на сервер с множеством проверок был бы неудобочитаем.
Декларативный парсинг строкСредств работы со строками и регулярными выражениями в Haskell не меньше чем в любом другом уважаемом языке, но есть способ лучше. Генераторы парсеров в Haskell имеют ярко выраженный вкус декларативности, поэтому в нижеследующем случае мы отложим ножницы в сторону и напишем небольшой парсер на Parsec для конвертации privacy_setting API в ADT.
Как видим реализация по компактности и понятности мало отличается от текстового описания.
ТестированиеДля тестирования используем популярный BDD пакет HSpec.
HSpec умеет искать тесты, выполнять инициализацию и очистку, имеет простой декларативный интерфейс. Тест для проверки OAuth авторизации ВКонтакте будет выглядеть следующим образом.
Браузерное приложение
Полный код пакета приведен здесь vk-api-example.
Наше небольшое приложение будет отображать и проигрывать в собственном плеере аудио пользователя, популярные треки, осуществлять поиск аудиозаписей.
-
— полноценный Haskell — почти полный Haskell — подмножество Haskell — Haskell с семантикой JavaScript — Haskell подобный, нишевый язык для браузерных приложений
Мы будем использовать GHCJS, где наш пакет API можно использовать без изменений.
Основой для построения интерфейса послужит пакет React-Flux байндингов к React/Flux.
React-Flux сохраняет семантику и архитектуру Flux приложений и использует те же именования компонентов.
Некоторые достоинства Haskell в применении к JavaScriptРассмотрим несколько достоинств, кроме очевидной строгой типизации, использования Haskell.
DSL для React, JSX не нуженВ силу компактности синтаксиса, использования монадического или аппликативного контекста вычислений Haskell является одним из чемпионов по производству DSL «из ниоткуда».
Сравним эквивалентные фрагменты кода AudioPlayer, портированного в наше приложение из JavaScript плеера react-audio-player, с оригиналом.
Читаемость кода эквивалентна, но во втором случае мы обходимся без специализированного транслятора, не выходим за рамки языка, имеем полную поддержку от средств разработки.
При портировании плеера я старался сохранять именования и логику, так что заинтересованный читатель сможет легко провести сравнение реализаций и сделать собственные выводы.
Решение проблемы «callback hell»- Рантайм GHCJS поддерживает весь внушительный арсенал Haskell в области параллельных/конкурентных вычислений. Мы можем писать код в обычной семантике конкурентных процессов, используя стандартные вызовы forkIO для их создания и обычные примитивы синхронизации Haskell — IORef, MVar, STM итд
- Специальный синтаксис оператора монадических вычислений do как раз и представляет собой транслятор последовательности вычислений во вложенные CPS-вызовы
- Упоминавшиеся ранее способы прерывания последовательности вычислений также помогают сделать из «лапши» красивое блюдо.
Так как приложение у нас одностраничное, то мы должны озаботиться использованием истории браузера. Создадим модуль Router.
Actions нашего приложения будут представлены типом ADT VKAction.
Для взаимного отображения URL из window.location.hash в ADT задействуем популярный пакет web-routes.
Соответсвующий макрос из пакета создаст код для такого маппинга.
Этого будет достаточно для преобразования Actions в URL, пример использования — создание линка.
Для реакции на изменение window.location.hash нам нужно будет навесить обработчик на window.onhashchange. FFI в GHCJS довольно прост, следующий код вряд ли нуждается в комментариях.
Модульность приложенияReact-Flux дает нам возможность создать несколько контроллеров, Store, со своими Actions и диспетчеризацией и далее организовать их совместную работу через конкурентные процессы.
Так виджет ввода поисковой строки IncrementalInput приложения использует таймер IdleTimer, который является полноценным контроллером со своими Store и Actions и работает независимо от основного контроллера приложения.
Тестирование приложенияДля тестирования приложения мы опять будем использовать HSpec и Selenium Webdriver через hspec-webdriver.
Пара скриншотов нашего скромного поделия.
Заключение
Надеюсь данный конспективный обзор послужит декларированной в начале цели.
Предваряя прогнозируемые пожелания читателей, пойду и убью себя сам о сад камней.