Как использовать "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

Что я пробовал?

  1. Я пытался преобразовать IO String в String (с помощью liftIO например).
  2. Я пытался найти подобные вопросы здесь.
  3. Я попытался найти похожий пример в ускоренном курсе Happstack.
  4. Я гуглил все связанные ключевые слова во всех различных комбинациях.

Заранее спасибо.

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 хотя, так что я могу ошибаться

Вот и все. Счастливого взлома!

Другие вопросы по тегам