简体   繁体   English

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

[英]How to use “IO String” as an HTTP response in Happstack?

I'm retrieving data from a database using HDBC, then trying to send this data to a web client using 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

When I build the above code I get this error: 构建以上代码时,出现此错误:

No instance for (ToMessage (IO String)) arising from a use of `toResponse' 没有因使用`toResponse'而产生(ToMessage(IO String))的实例

What did I try ? 我尝试了什么?

  1. I tried to convert the IO String to String (using liftIO for example). 我试图将IO String转换为String (例如,使用liftIO )。
  2. I tried to find any similar questions here. 我试图在这里找到任何类似的问题。
  3. I tried to find a similar example in the Happstack Crash Course. 我试图在Happstack速成课程中找到类似的示例。
  4. I googled all related keywords in all different combinations. 我用所有不同的组合搜索了所有相关的关键字。

Thanks in advance. 提前致谢。

You have to design your handlers around the fact that fetching from a database is a magical action that may not give you what you expect. 您必须根据以下事实来设计handlers :从数据库中获取数据是一种神奇的动作 ,可能无法满足您的期望。 (For example, your database may crash.) This is why its result is served as an IO , which is a particular case of a monad . (例如,您的数据库可能崩溃。)这就是为什么其结果用作IO ,这是monad的一种特殊情况。

A monad is a jar with a very narrow neck, so narrow even that, once you put something in there, you cannot unput it. 单子是一个非常狭窄的颈部的罐子,即使狭窄到将某物放入其中也无法解开的状态。 (Unless it happens to also be a comonad , but that's a whole another story and not the case with IO nor with ServerPart .) So, you would never convert an IO String to a String . (除非它碰巧也是一个comonad ,但这又是另外一回事了, IOServerPart并非如此。)因此,您永远不会将IO String转换为String Not that you can't, but your program would become incorrect. 并非您不能,但是您的程序将变得不正确。

Your case is kind of tricky as you have two monads at play there: IO and ServerPart . 您的案例有些棘手,因为那里有两个monad: IOServerPart Fortunately, ServerPart builds upon IO , it is " larger " and can, in a sense, absorb IO : we can put some IO into a ServerPart and it will be a ServerPart still, so we may then give it to simpleHTTP . 幸运的是, ServerPart建立在IO之上,从某种意义上讲,它是“ 更大的 ”并且可以吸收 IO :我们可以将一些IO放入ServerPart而它将仍然是ServerPart ,因此可以将其提供给simpleHTTP In happstack , this conversion may be done via require function, but there is a more general solution as well, involving monad transformers and lift . happstack ,可以通过require函数完成此转换,但是还有一个更通用的解决方案,涉及monad转换器lift

Let's take a look at the solution with require first. 让我们看一下先使用require解决方案 Its type (simplified to our case) is: 其类型(简化为我们的情况)为:

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

— So, it takes an IO jar with some argument and makes it suitable for a function that lives in the ServerPart jar. —因此,它需要一个带有某些参数的IO jar,并使它适合于ServerPartServerPart jar中的函数。 We just have to adjust types a bit and create one lambda abstraction : 我们只需要稍微调整一下类型并创建一个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

As you see, we have to make 2 modifications: 如您所见,我们必须进行2处修改:

  • Adjust myFunc so that it returns Maybe , as necessitated by require . 调整myFunc ,以便它返回Maybe ,作为必要的require This is a better design because myFunc may now fail in two ways: 这是一个更好的设计,因为myFunc现在可能会以两种方式失败:

    • As a Maybe , it may return Nothing , which means 404 or the like. 作为Maybe ,它可能会返回Nothing ,这意味着404之类。 This is rather common a situation. 这是相当普遍的情况。
    • As an IO , it may error out, which means the database crashed. 作为IO ,它可能会出错,这意味着数据库崩溃了。 Now is the time to alert the DevOps team. 现在是时候提醒DevOps团队了。
  • Adjust handlers so that myFunc is external to them. 调整handlers使myFunc在它们外部。 One may say more specifically: abstract myFunc from handlers . 可能更具体地说: handlers提取myFunc This is why this syntax is called a lambda abstraction . 这就是为什么此语法称为lambda 抽象的原因

require is the way to deal with monads in happstack specifically. require是专门处理happstack monad的happstack Generally though, this is just a case of transforming monads into larger ones, which is done via lift . 通常,这只是 monad转换为更大的情况,这是通过lift The type of lift (again, simplified), is: lift的类型(再次简化)是:

IO String -> ServerPart String

So, we can just lift the myFunc 1 :: IO String value to the right monad and then compose with >>= , as usual: 所以,我们就可以liftmyFunc 1 :: IO String值,以正确的单子,然后用撰写>>= ,像往常一样:

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

As simple as that. 就如此容易。 I used the same lambda abstraction trick again, but you may as well use do-notation : 我再次使用了相同的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 Returning to the story of large and small jars: you can put IO into ServerPart precisely because ServerPart is also an IO monad — it is an instance of the MonadIO class . PS回到大罐子和小罐子的故事:您可以将IO放入ServerPart正是因为ServerPart也是IO monad —它是MonadIO的实例。 That means that anything you can do in IO you can also do in ServerPart , and, besides general lift , there is a specialized liftIO function that you can use everywhere I used lift . 这意味着,任何你能在做IO ,你也可以做ServerPart ,并且,除了一般的lift ,有一个专门的liftIO ,你可以用我到处使用的功能lift You are likely to meet many other monads out there that are instances of MonadIO as it's a handy way of structuring code in large applications. 您可能会遇到许多其他MonadIO ,它们是MonadIO实例,因为它是在大型应用程序中结构化代码的便捷方法。

In your particular case, I would stick with the require way nevertheless because I think it's how the designers of happstack meant it to be done. 在您的特定情况下,我还是会坚持使用require方法,因为我认为这是happstack的设计者认为happstack这一点的方式。 I'm not particularly knowledgeable about happstack though, so I may be wrong. 我对happstack知识不是特别了解,所以我可能是错的。

That's it. 而已。 Happy hacking! 骇客入侵!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM