>我正在使用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 字符串)) 的实例因使用 "响应">
我试了什么?
- 我尝试将
IO String
转换为String
(例如使用liftIO
)。 - 我试图在这里找到任何类似的问题。
- 我试图在Happstack速成课程中找到类似的例子。
- 我用谷歌搜索了所有不同组合的所有相关关键字。
提前谢谢。
你必须围绕这样一个事实来设计你的handlers
,即从数据库中获取是一个神奇的操作,可能不会给你期望的。(例如,数据库可能会崩溃。这就是为什么它的结果是作为IO
,这是单子的一个特例。
monad是一个脖子很窄的罐子,甚至很窄,一旦你把东西放进去,你就无法解开它。(除非它碰巧也是comonad
,但那是另一回事,IO
和ServerPart
都不是这样。因此,您永远不会将IO String
转换为String
。不是说你不能,而是你的程序会变得不正确。
你的情况有点棘手,因为你有两个monad在那里发挥作用:IO
和ServerPart
。幸运的是,ServerPart
建立在IO
的基础上,它是">更大的",在某种意义上可以吸收IO
:我们可以把一些IO
放进一个ServerPart
,它仍然会是一个ServerPart
,所以我们可以把它交给simpleHTTP
。在happstack
中,这种转换可以通过require
函数来完成,但也有一个更通用的解决方案,涉及单元变压器和lift
。
让我们先看一下require
的解决方案。它的类型(简化为我们的情况)是:
IO (Maybe a) -> (a -> ServerPart r) -> ServerPart r
— 因此,它需要一个带有一些参数的IO
罐,并使其适合存在于ServerPart
罐中的函数。我们只需要稍微调整类型并创建一个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
在它们外部。可以更具体地说:来自handlers
的抽象myFunc
。这就是为什么这种语法被称为lambda抽象。
require
是专门处理happstack
中的单子的方法。不过,一般来说,这只是将单子转换为较大单子的情况,这是通过lift
.lift
的类型(再次简化)为:
IO String -> ServerPart String
因此,我们可以将myFunc 1 :: IO String
值lift
到正确的 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表示法:
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
附言回到大罐子和小罐子的故事:你可以把IO
放进ServerPart
,ServerPart
正是因为它也是一个IO
的monad——它是MonadIO
类的一个实例。这意味着你可以在IO
中做的任何事情你也可以在ServerPart
中做,而且,除了一般lift
,还有一个专门的liftIO
功能,你可以在我lift
使用的任何地方使用。您可能会遇到许多其他 monads 是MonadIO
实例,因为它是在大型应用程序中构建代码的便捷方式。
在你的特殊情况下,我会坚持require
的方式,因为我认为这就是happstack
的设计者想要这样做的方式。不过,我对happstack
不是特别了解,所以我可能是错的。
就是这样。快乐黑客!