1

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

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

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

我尝试了什么?

  1. 我试图将其转换IO StringStringliftIO例如使用)。
  2. 我试图在这里找到任何类似的问题。
  3. 我试图在 Happstack Crash Course 中找到一个类似的例子。
  4. 我搜索了所有不同组合中的所有相关关键字。

提前致谢。

4

1 回答 1

6

您必须handlers围绕以下事实进行设计:从数据库中获取是一种神奇的操作,可能无法满足您的期望。(例如,您的数据库可能会崩溃。)这就是为什么它的结果被用作IO,这是monad的一种特殊情况。

monad 是一个颈部非常狭窄的罐子,甚至如此狭窄,一旦您将某些东西放入其中,就无法将其取出。(除非它碰巧也是 a comonad,但那完全是另一回事了,而不是这样,IO也不是这样ServerPart。)所以,你永远不会将 an 转换IO String为 a String。不是你不能,而是你的程序会变得不正确。

你的情况有点棘手,因为你有两个单子在玩:IOServerPart. 幸运的是,ServerPart基于IO,它“更大”,并且在某种意义上可以吸收 IO:我们可以将一些IO放入 aServerPart中,它会是ServerPart静止的,因此我们可以将它交给simpleHTTP。在happstack中,这种转换可以通过require函数来​​完成,但也有一个更通用的解决方案,涉及monad 转换器和lift.

 

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

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

— 因此,它需要一个IO带有一些参数的 jar,并使其适合存在于ServerPartjar 中的函数。我们只需要稍微调整类型并创建一个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现在可能会以两种方式失败:

    • 作为 a Maybe,它可能会返回Nothing,这意味着404等。这是比较常见的情况。
    • 作为一个IO,它可能会出错,这意味着数据库崩溃了。现在是提醒 DevOps 团队的时候了。
  • 进行调整handlers,使其myFunc对他们来说是外部的。可以更具体地说:myFunc. handlers这就是为什么这种语法被称为 lambda abstraction的原因。

 

require是专门处理单子的方法happstack。不过,一般来说,这只是将 monad 转换为更大的单子的情况通过lift. lift(再次简化)的类型是:

IO String -> ServerPart String

所以,我们可以像往常一样只liftmyFunc 1 :: IO String正确的 monad 赋值,然后用 compose >>=

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它也是一个IOmonad——它是MonadIO的一个实例。这意味着您可以在IO您中执行的任何操作ServerPart,并且,除了通用之外lift,还有一个专门的liftIO功能,您可以在我使用的任何地方使用lift。您可能会遇到许多其他 monad 的实例,MonadIO因为它是在大型应用程序中构建代码的便捷方式。

在您的特定情况下,我仍然会坚持这种require方式,因为我认为这是设计者的happstack意图。虽然我不是特别了解happstack,所以我可能是错的。

 

而已。快乐黑客!

于 2018-02-16T08:14:05.780 回答