如何在 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'
我尝试了什么?
- 我尝试将
IO String
转换为String
(例如使用liftIO
)。 - 我试图在这里找到任何类似的问题。
- 我试图在 Happstack 速成课程中找到类似的示例。
- 我用谷歌搜索了所有不同组合的所有相关关键字。
提前致谢。
您必须围绕这样一个事实来设计您的 handlers
,即从数据库中获取数据是一个 IO
,这是 monad.
monad 是一个脖子很窄的罐子,窄到,一旦你把东西放进去,就不能再放了。 (除非它碰巧也是 comonad
,但那完全是另一回事了,IO
和 ServerPart
都不是这样。) 所以,您永远不会将 IO String
转换为 String
。不是你不能,而是你的程序会变得不正确。
你的情况有点棘手,因为你有两个单子在起作用:IO
和 ServerPart
。幸运的是,ServerPart
建立在IO
之上,它“更大”并且在某种意义上可以吸收IO
:我们可以将一些 IO
放入 ServerPart
中,它仍然是 ServerPart
,因此我们可以将它交给 simpleHTTP
。在 happstack
中,这种转换可以通过 require
函数完成,但也有一个更通用的解决方案,涉及 monad transformers 和 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
使其 returnsMaybe
,如require
所需要。这是一个更好的设计,因为myFunc
现在可能会以两种方式失败:- 作为
Maybe
,可能是returnNothing
,表示404
之类的。这种情况比较常见。 - 作为一个
IO
,它可能会出错,这意味着数据库崩溃了。现在是提醒 DevOps 团队的时候了。
- 作为
调整
handlers
,使myFunc
在他们之外。可以更具体地说:从handlers
抽象myFunc
。这就是为什么这种语法被称为 lambda abstraction.
require
是 happstack
中专门处理 monad 的方法。不过,一般来说,这只是 将 monads 转换为更大的 monads 的情况,这是通过 通过 lift
[ 完成的=131=]。 lift
的类型(再次简化)是:
IO String -> ServerPart String
所以,我们可以 lift
将 myFunc 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
并不是特别了解,所以我可能是错的。
就是这样。快乐黑客!