我正在使用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'
我尝试了什么?
IO String
转换为String
(例如,使用liftIO
)。 提前致谢。
答案 0 :(得分:6)
你必须设计你的handlers
围绕这样一个事实:从数据库中提取magical action可能无法满足你的期望。 (例如,您的数据库可能会崩溃。)这就是为什么它的结果作为IO
提供的原因,这是 monad 的特殊情况。
monad是一个颈部非常狭窄的罐子,即便如此,一旦你把东西放在那里,你就无法输出它。 (除非它恰好也是comonad
,但这是另一个故事而不是IO
或ServerPart
的情况。)所以,你会永远不会将IO String
转换为String
。不是你不能,但你的程序会变得不正确。
你的情况有点棘手,因为你在那里有两个monad:IO
和ServerPart
。幸运的是,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
因此,我们可以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
PS 回到大小罐子的故事:您可以将IO
放入ServerPart
,因为ServerPart
也是 IO
1}} monad - 它是MonadIO
class的一个实例。这意味着您可以在IO
中执行的任何事情也可以在ServerPart
中执行,除了一般lift
之外,还有一个专门的liftIO
函数,您可以在任何地方使用我使用lift
。您可能会遇到许多其他monad,它们是MonadIO
的实例,因为它是在大型应用程序中构造代码的便捷方式。
在你的特定情况下,我会坚持使用require
方式,因为我认为这是happstack
的设计者意味着要完成的。我对happstack
并不是特别了解,所以我可能错了。
就是这样。快乐的黑客!