问题
I'm retrieving data from a database using HDBC, then trying to send this data to a web client using Happstack.
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'
What did I try ?
- I tried to convert the
IO String
toString
(usingliftIO
for example). - I tried to find any similar questions here.
- I tried to find a similar example in the Happstack Crash Course.
- I googled all related keywords in all different combinations.
Thanks in advance.
回答1:
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. (For example, your database may crash.) This is why its result is served as an IO
, which is a particular case of a 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
. 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
. 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
. In happstack
, this conversion may be done via require function, but there is a more general solution as well, involving monad transformers and lift
.
Let's take a look at the solution with require
first. 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. We just have to adjust types a bit and create one lambda abstraction:
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:
Adjust
myFunc
so that it returnsMaybe
, as necessitated byrequire
. This is a better design becausemyFunc
may now fail in two ways:- As a
Maybe
, it may returnNothing
, which means404
or the like. This is rather common a situation. - As an
IO
, it may error out, which means the database crashed. Now is the time to alert the DevOps team.
- As a
Adjust
handlers
so thatmyFunc
is external to them. One may say more specifically: abstractmyFunc
fromhandlers
. This is why this syntax is called a lambda abstraction.
require
is the way to deal with monads in happstack
specifically. Generally though, this is just a case of transforming monads into larger ones, which is done via lift. The type of lift
(again, simplified), is:
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:
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:
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. 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. 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
. 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.
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. I'm not particularly knowledgeable about happstack
though, so I may be wrong.
That's it. Happy hacking!
来源:https://stackoverflow.com/questions/48820722/how-to-use-io-string-as-an-http-response-in-happstack