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

时间:2018-02-16 05:41:38

标签: haskell happstack hdbc

我正在使用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

当我构建上面的代码时,我收到了这个错误:

  

因使用而产生的(ToMessage(IO String))没有实例   `toResponse'

我尝试了什么?

  1. 我尝试将IO String转换为String(例如,使用liftIO)。
  2. 我试图在这里找到任何类似的问题。
  3. 我试图在Happstack Crash Course中找到一个类似的例子。
  4. 我用各种不同的组合搜索了所有相关的关键字。
  5. 提前致谢。

1 个答案:

答案 0 :(得分:6)

你必须设计你的handlers围绕这样一个事实:从数据库中提取magical action可能无法满足你的期望。 (例如,您的数据库可能会崩溃。)这就是为什么它的结果作为IO提供的原因,这是 monad 的特殊情况。

monad是一个颈部非常狭窄的罐子,即便如此,一旦你把东西放在那里,你就无法输出它。 (除非它恰好也是comonad,但这是另一个故事而不是IOServerPart的情况。)所以,你会永远不会将IO String转换为String。不是你不能,但你的程序会变得不正确。

你的情况有点棘手,因为你在那里有两个monad:IOServerPart。幸运的是,ServerPart构建于IO之后,它是“更大”,从某种意义上说,吸收 IO:我们可以将一些IO放入ServerPart,它仍然是ServerPart,因此我们可以将其提交给simpleHTTP。在happstack中,此转换可以通过require函数完成,但也有一个更通用的解决方案,涉及 monad变换器lift

让我们首先看一下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的需要调整Maybe,使其返回require。这是一个更好的设计,因为myFunc现在可能以两种方式失败:

    • 作为Maybe,它可能会返回Nothing,这意味着404等。这种情况很常见。
    • 作为IO,它可能会出错,这意味着数据库崩溃了。现在是时候提醒DevOps团队了。
  • 调整handlers,以便myFunc在他们之外。可以更具体地说:来自myFunc 的摘要handlers。这就是为什么这种语法称为lambda 抽象

require是专门处理happstack中monad的方法。但一般情况下,这只是 monads 转换为更大的一个通过lift 完成的情况。 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

PS 回到大小罐子的故事:您可以将IO放入ServerPart,因为ServerPart也是 IO 1}} monad - 它是MonadIO class的一个实例。这意味着您可以在IO中执行的任何事情也可以在ServerPart中执行,除了一般lift之外,还有一个专门的liftIO函数,您可以在任何地方使用我使用lift。您可能会遇到许多其他monad,它们是MonadIO的实例,因为它是在大型应用程序中构造代码的便捷方式。

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

就是这样。快乐的黑客!