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
to String
(using liftIO
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.
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 returns Maybe
, as necessitated by require
. This is a better design because myFunc
may now fail in two ways:
- As a
Maybe
, it may return Nothing
, which means 404
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.
Adjust handlers
so that myFunc
is external to them. One may say more specifically: abstract myFunc
from handlers
. 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!