Шрифт
Haskell для ВКонтакте, JavaScript и ReactJS, Или «Чужой против Симпсонов»
Play

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-вызовы
  • Упоминавшиеся ранее способы прерывания последовательности вычислений также помогают сделать из «лапши» красивое блюдо.
Маршрутизация в приложении, используем FFI

Так как приложение у нас одностраничное, то мы должны озаботиться использованием истории браузера. Создадим модуль 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.

Пара скриншотов нашего скромного поделия.

Заключение

Надеюсь данный конспективный обзор послужит декларированной в начале цели.

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

📎📎📎📎📎📎📎📎📎📎