О погоде функционально

Недавно, очередной раз взглянув за окно, вдруг подумал: “Интересно, а сколько там градусов за окном?”. Сегодня, имея браузер под рукой, ответить на этот вопрос несложно, и даже точность прогноза будет вполне приличной. Однако даже этот способ имеет недостаток - слишком много телодвижений! И конечно же вожжа попала под нужное место: я решил написать простенькую программку, запрашивающую погоду. Ну и, как водится, на Haskell

Выводить погоду я решил в статусную строку моего оконного менеджера (i3wm), а информация, выводимая на оной, должна быть исключительно текстовой. Для краткого прогноза в стиле -5, облачно вполне подходит. Напросились следующие требования к программе:

  • CLI-интерфейс
  • вывод погоды как для заданного города (на момент написания, это была Казань), так и для любого другого - указанием через опции командной строки
  • получение данных о погоде от Yahoo weather (выбор пал на этот сервис, т.к. этот API был уже знаком)
  • возможность добавлять в дальнейшем дополнительные сервисы, предоставляющие данные о погоде

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

  • запрашивать что-то через HTTP
  • парсить XML
  • реализовывать CLI-опции

В таком порядке я реализовал функции приложения, буду придерживаться этого порядка и здесь. Но с начала

“Типа, типы”

Haskell учит: Сначала типы, постом всё остальное!

Посему:

Данные о погоде хранятся преимущественно в полях типа Text, т.к. могут содержат символы Unicode, да и получаться будут из XML, текст в котором хранится именно в Text.

Т.к. погоду предстояло выводить в текстовом виде, реализовал приведение Weather к строке. Вот как это выглядит:

Вывод же получается следующим

Fri, 21 Nov 2014 9:59 pm MSK: Kazan'(Russia), -5°C, Fog

Правда, в конце концов я решил, что выглядит такая “портянка” громоздко и, путем комментирования первых трёх строк, сократил выводимый текст до -5°C, Fog

Ну вот, данные есть, теперь можно вернуться к первоначальному плану. Итак

“Ну и запросы у вас!”

Работа с протоколом HTTP в Haskell обычно делается силами пакета… HTTP! Кхм, даже скучно как-то. Первый вариант функции отправки запроса с возвратом тела ответа выглядел так:

Здесь всё довольно просто: результатом неудавшегося запросы будет просто Nothing, а в случае удачи вернётся Just "..." с телом ответа.

Url же формировать было поручено этой функции:

Здесь тоже всё прозрачно, я считаю.

Этот вариант HTTP-клиента работал у меня отлично дома, однако через офисный proxy-сервер пробиться сходу он, увы, не смог. Ну да ладно, на то он и simpleHTTP - ему простительно. Гугление подсказало, что через прокси может ходить Network.HTTP.Browser. Решено было его и использовать. Теперь код запроса погоды выглядит так:

“Что-то неразборчив ваш XML, без линз никак!”

XML-документы в Haskell хранятся в типе Document из модуля Text.XML. Получить же документ из строки с содержимым можно так:

parseText возвращает Either, содержащий описание ошибок парсинга. Однако, данная задача не требует таких подробностей, поэтому функция, приведенная выше, возвращает просто Maybe Document.

Документ получать уже можно, но ведь нужно ещё и работать с ним. Будучи знаком с линзами, я прямо таки чувствовал, что XML можно обрабатывать и с их помощью. Так и вышло: нашелся пакет xml-lens! Разбор документа далее будет производиться с его помощью:

XML-линзы выглядят необычно, но для тем, кто знаком с линзами в целом, разобраться труда не составит. Сам код же вполне читаем получился, что особо порадовало.

“Также доступны следующие опции…”

Опции программы нужно где-то хранить, для этой цели был добавлен тип:

Командную строку я решил разбирать с помощью библиотеки optparse-applicative. Встретил её я какое то время назад, но использовать пакет ещё не доводилось. Здесь же библиотека очень пригодилась. Парсер опций с выглядит так:

Сам же интерфейс командной строки описывается так:

Библиотека, кроме собственно реализации опций, даёт ещё и возможность генерировать автоматически справку по ключам и опциям. Выглядит справка так:

$ weather --help
weather - Yahoo Weather displaying tool

Usage: weather [-c|--city CITY] [-F|--farenheits] [-p|--proxy ARG]
  Print current weather for CITY

Available options:
  -h,--help                Show this help text
  -c,--city CITY           Yahoo weather API's city ID
  -F,--farenheits          Show temperature in Farenheits (default: Celsiuses)
  -p,--proxy ARG           Proxy server in format [user:[email protected]]host[:port]

“Всех их вместе соберем!”

Ну вот, в общем то, и всё, что нужно для сборки готовой программы. Осталась самая малость - main-функция:

Всё!

Вот и готово полезное приложение, и, что ещё важнее, опробованы удобные и мощные инструменты! Целиком же код можно посмотреть тут.