Статьи

Написание REST-клиента на Хаскеле

Несколько дней назад я решил купить биткойн. Затем я заметил, что он сильно колеблется, несмотря на общую тенденцию к росту. Хммм … если бы я просто купил в нужный момент и продал в другой нужный момент, я мог бы заставить деньги выпасть из пустоты!

С тех пор я потерял 5 долларов, играя, и получил 30 долларов, оставив его в покое. Очевидно, я отстой в этом …

Я знаю! Давайте сделаем бот, который делает это! Низкочастотный торговый бот, это звучит как веселье. И давайте напишем это на Хаскеле , просто чтобы все было интересно. Объединение самых строгих языков с самыми грязными ресурсами — Интернетом … что может пойти не так?

Первый заказ бизнеса — клиент REST.


Куратор добавил это изображение для развлечения.

REST клиент

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

Написание REST-клиента на большинстве языков очень просто. Чтение тикера Bitstamp в Python выглядит следующим образом:

import requests, json
 
r = requests.get("https://www.bitstamp.net/api/ticker")
print json.loads(r.content)
# prints: {u'volume': u'17179.28558844', u'last': u'159.49', u'bid': u'159.49', u'high': u'161.00', u'low': u'139.00', u'ask': u'159.64'}

Вот и все. Все, что вам нужно для набора значений, доступно в виде словаря.

В Хаскеле, хорошо в Хаскеле, выяснение того, как это сделать, заняло у меня всю ночь, затем немного утра и, наконец, полезный твит от незнакомца, чтобы рассказать мне, как я неправильно использовал монады.

Прежде всего, нам понадобится куча импорта. Те , кого мы  действительно заботимся о том, являются  HTTP-канал библиотеки и  эсон  парсер JSON строки. Все остальное есть, потому что… ну, на самом деле я не уверен, но это кажется необходимым, иначе вещи не работают.

{-# LANGUAGE OverloadedStrings #-}
 
import Network.HTTP.Conduit
import Control.Monad.IO.Class
import Data.ByteString.Lazy
import Data.Aeson
import Data.Attoparsec.Number
import Control.Applicative
import Control.Monad.Trans

Я не совсем уверен, что делает бит OverloadedStrings в верхней части. Это своего рода директива компилятора, и большинство библиотек haskell, которые я нахожу в дикой природе, говорят мне, что я буду намного счастливее, если я включу его. Shrug.

Все, что требуется сейчас, это сделать HTTP-запрос и проанализировать ответ как JSON. Просто, правда?

Ну, Хаскелл строг, и ты не можешь просто разобрать все, что бы ни случилось. Мы должны сказать анализатору , что мы ожидаем, что мы хотим сделать с результатом и что  типа это будет. Не может быть просто мешок с вещами! Нет, должен быть четко определенный пакет вещей.

data Ticker = Ticker
              { high :: Number,
                last :: Number,
                bid :: Number,
                volume :: Number,
                low :: Number,
                ask :: Number
              } deriving Show

Отлично. У нас есть  тип тикера, который имеет несколько чисел и несколько имен. Эта часть шоу,  похоже, говорит, что мы сможем распечатать это на консоли. Smashing!

Этого недостаточно, но пришло время для некоторых странных иероглифов, которые говорят Эсону, как именно работает синтаксический анализ.

instance FromJSON Ticker where
  parseJSON (Object v) = Ticker 
                         v .: "high" 
                         v .: "last" 
                         v .: "bid" 
                         v .: "volume" 
                         v .: "low" 
                         v .: "ask"

Если я правильно понимаю, эти странные символы являются аппликативными. :.  Делает … что — то … и  <$>  и  <*>  сделать что — то еще. Все дело в том, как определить, как преобразовать ключ в строке JSON в значение  типа Ticker . Думаю.

Хорошо, давайте создадим функцию, которая будет общаться с сервером и возвращать  объект Ticker  . Может быть. Если все пойдет хорошо.

ticker::(MonadIO m) > m (Maybe Ticker)
ticker = get "ticker" >>= return . decode

Довольно простые вещи. Возьмите что-нибудь из Интернета, аккуратно завернутый в  MonadIO , немного разверните его , вставьте в  декодирование , которое волшебным образом использует все вещи, которые мы определили ранее, и оберните это обратно в  MonadIO и  Maybe. Парсинг может не сработать, ты знаешь.

get::(MonadIO m) => String > m ByteString
get url = simpleHttp $ "https://www.bitstamp.net/api/"++url

Это обобщенная  функция get, которая общается с Bitstamp с помощью  функции simpleHttp из  http-проводника . Это выглядит просто, но я уверен, что за кулисами происходит много волосатых вещей.

Чтобы убедиться, что все работает, мы запускаем его.

main = do
  ticker >>= print

Ничего.

Да, результат, который мы получаем, Ничего. Именно в этот момент вы понимаете, что  кто-то неправильно использует JSON, и все эти числа на самом деле являются строками. Строки. Как, черт возьми, вы говорите Haskell, чтобы автоматически преобразовать их в числа перед тем, как поместить их в объект Ticker?

Грязный грязный интернет.

Но эй! Получил Haskell, чтобы поговорить с REST API . Как это круто!?