Как использовать "IO String" в качестве ответа HTTP в Happstack?
Я извлекаю данные из базы данных с использованием HDBC, а затем пытаюсь отправить эти данные в веб-клиент с помощью Happstack.
myFunc :: Integer -> IO String
myFunc = ... fetch from db here ...
handlers :: ServerPart Response
handlers =
do decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
msum [
dir "getData" $ ok $ toResponse $ myFunc $ toInteger 1
]
mainFunc = simpleHTTP nullConf handlers
Когда я строю код выше, я получаю эту ошибку:
Нет экземпляра для (ToMessage (IO String)), возникающего из-за использования toResponse
Что я пробовал?
- Я пытался преобразовать
IO String
вString
(с помощьюliftIO
например). - Я пытался найти подобные вопросы здесь.
- Я попытался найти похожий пример в ускоренном курсе Happstack.
- Я гуглил все связанные ключевые слова во всех различных комбинациях.
Заранее спасибо.
1 ответ
Вы должны разработать свой handlers
вокруг факта, что выборка из базы данных является магическим действием, которое может не дать вам того, что вы ожидаете. (Например, ваша база данных может произойти сбой.) Вот почему ее результат служит IO
, что является частным случаем монады.
Монада - это банка с очень узким горлышком, настолько узким, что, если вы положите туда что-то, вы не сможете извлечь его. (Если это не происходит также comonad
, но это совсем другая история, а не случай с IO
ни с ServerPart
.) Таким образом, вы никогда не конвертируете IO String
к String
, Не то чтобы вы не могли, но ваша программа стала бы неправильной.
Ваш случай довольно сложный, так как у вас там две монады: IO
а также ServerPart
, К счастью, ServerPart
основывается на IO
, он " больше " и может в некотором смысле поглощать IO
: мы можем поставить некоторые IO
в ServerPart
и это будет ServerPart
тем не менее, поэтому мы можем затем дать его simpleHTTP
, В happstack
это преобразование может быть сделано через require
функции, но есть и более общее решение, включающее монадные трансформаторы и lift
,
Давайте посмотрим на решение с require
первый. Его тип (упрощенный для нашего случая):
IO (Maybe a) -> (a -> ServerPart r) -> ServerPart r
- Итак, требуется IO
банку с некоторым аргументом и делает его подходящим для функции, которая живет в ServerPart
баночка. Нам просто нужно немного настроить типы и создать одну лямбда-абстракцию:
myFunc :: Integer -> IO (Maybe String)
myFunc _ = return . Just $ "A thing of beauty is a joy forever."
handlers :: ServerPart Response
handlers = require (myFunc 1) $ \x ->
do decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
msum [
dir "getData" $ ok $ toResponse x
]
mainFunc = simpleHTTP nullConf handlers
Как видите, мы должны сделать 2 модификации:
регулировать
myFunc
так что это возвращаетMaybe
как того требуетrequire
, Это лучший дизайн, потому чтоmyFunc
может теперь потерпеть неудачу двумя способами:- Как
Maybe
может вернутьсяNothing
, что значит404
или т.п. Это довольно распространенная ситуация. - Как
IO
, это может привести к ошибке, что означает сбой базы данных. Настало время предупредить команду DevOps.
- Как
регулировать
handlers
чтобыmyFunc
является внешним для них. Можно сказать более конкретно: аннотацияmyFunc
отhandlers
, Вот почему этот синтаксис называется лямбда- абстракцией.
require
это способ иметь дело с монад в happstack
в частности. Хотя, в общем, это всего лишь случай преобразования монад в более крупные, что делается с помощью lift
, Тип lift
(опять же упрощенно), это:
IO String -> ServerPart String
Итак, мы можем просто lift
myFunc 1 :: IO String
значение к правой монаде, а затем составить с >>=
, по-прежнему:
myFunc :: Integer -> IO String
myFunc _ = return $ "Its loveliness increases,.."
handlers :: ServerPart Response
handlers = lift (myFunc 1) >>= \x ->
do decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
msum [
dir "getData" $ ok $ toResponse x
]
mainFunc = simpleHTTP nullConf handlers
Так просто. Я снова использовал тот же трюк с лямбда-абстракцией, но вы также можете использовать do-notation:
myFunc :: Integer -> IO String
myFunc _ = return $ "...it will never pass into nothingness."
handlers :: ServerPart Response
handlers = do
x <- lift (myFunc 1)
decodeBody (defaultBodyPolicy "/tmp/" 0 1000 1000)
msum [
dir "getData" $ ok $ toResponse x
]
mainFunc = simpleHTTP nullConf handlers
PS Возвращаясь к истории больших и маленьких баночек: можно поставить IO
в ServerPart
именно потому, что ServerPart
также IO
монада - это пример MonadIO
класс Это означает, что все, что вы можете сделать в IO
Вы также можете сделать в ServerPart
и, кроме общего lift
есть специализированная liftIO
функция, которую вы можете использовать везде, где я использовал lift
, Вы, вероятно, встретите много других монад, которые являются примерами MonadIO
так как это удобный способ структурирования кода в больших приложениях.
В вашем конкретном случае я бы придерживался require
тем не менее, потому что я думаю, что это, как дизайнеры happstack
хотел это сделать. Я не особенно осведомлен о happstack
хотя, так что я могу ошибаться
Вот и все. Счастливого взлома!