如何在 Happstack 中使用 "IO String" 作为 HTTP 响应?

How to use "IO String" as an HTTP response in Happstack?

我正在使用 HDBC 从数据库中检索数据,然后尝试使用 Happstack 将此数据发送到 Web 客户端。

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

当我构建上述代码时出现此错误:

No instance for (ToMessage (IO String)) arising from a use of `toResponse'

我尝试了什么?

  1. 我尝试将 IO String 转换为 String(例如使用 liftIO)。
  2. 我试图在这里找到任何类似的问题。
  3. 我试图在 Happstack 速成课程中找到类似的示例。
  4. 我用谷歌搜索了所有不同组合的所有相关关键字。

提前致谢。

您必须围绕这样一个事实来设计您的 handlers,即从数据库中获取数据是一个 ,可能无法满足您的期望。 (例如,您的数据库可能会崩溃。)这就是为什么它的结果被用作 IO,这是 monad.

的特例

monad 是一个脖子很窄的罐子,窄到,一旦你把东西放进去,就不能再放了。 (除非它碰巧也是 comonad,但那完全是另一回事了,IOServerPart 都不是这样。) 所以,您永远不会将 IO String 转换为 String。不是你不能,而是你的程序会变得不正确。

你的情况有点棘手,因为你有两个单子在起作用:IOServerPart。幸运的是,ServerPart建立在IO之上,它“更大”并且在某种意义上可以吸收IO:我们可以将一些 IO 放入 ServerPart 中,它仍然是 ServerPart,因此我们可以将它交给 simpleHTTP。在 happstack 中,这种转换可以通过 require 函数完成,但也有一个更通用的解决方案,涉及 monad transformerslift

我们先来看看require的解法。它的类型(简化为我们的例子)是:

IO (Maybe a) -> (a -> ServerPart r) -> ServerPart r

— 因此,它需要一个带有一些参数的 IO jar,并使其适用于存在于 ServerPart jar 中的函数。我们只需要稍微调整类型并创建一个 lambda 抽象:

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 使其 returns Maybe,如 require 所需要。这是一个更好的设计,因为 myFunc 现在可能会以两种方式失败:

    • 作为Maybe,可能是returnNothing,表示404之类的。这种情况比较常见。
    • 作为一个IO,它可能会出错,这意味着数据库崩溃了。现在是提醒 DevOps 团队的时候了。
  • 调整handlers,使myFunc在他们之外。可以更具体地说:handlers抽象myFunc。这就是为什么这种语法被称为 lambda abstraction.

requirehappstack 中专门处理 monad 的方法。不过,一般来说,这只是 monads 转换为更大的 monads 的情况,这是通过 通过 lift[ 完成的=131=]。 lift 的类型(再次简化)是:

IO String -> ServerPart String

所以,我们可以 liftmyFunc 1 :: IO String 值赋给正确的 monad,然后像往常一样与 >>= 组合:

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

就这么简单。我再次使用了相同的 lambda 抽象技巧,但你也可以使用 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

P.S. 回到大小罐的故事:可以把IO放入ServerPart正是因为ServerPart 也是 一个 IO monad — 它是 MonadIO class 的一个实例。这意味着你在 IO 中可以做的任何事情你也可以在 ServerPart 中做,而且,除了一般的 lift,还有一个专门的 liftIO 函数,你可以在任何地方使用我使用 lift。您可能会遇到许多其他的 monad,它们是 MonadIO 的实例,因为它是在大型应用程序中构建代码的便捷方式。

在您的特定情况下,我会坚持使用 require 方式,因为我认为这是 happstack 的设计者的意思。不过,我对 happstack 并不是特别了解,所以我可能是错的。

就是这样。快乐黑客!