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.
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 anIO
, 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 withIO
nor withServerPart
.) So, you would never convert anIO String
to aString
. 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
andServerPart
. Fortunately,ServerPart
builds uponIO
, it is " larger " and can, in a sense, absorbIO
: we can put someIO
into aServerPart
and it will be aServerPart
still, so we may then give it tosimpleHTTP
. Inhappstack
, this conversion may be done viarequire
function, but there is a more general solution as well, involving monad transformers andlift
.Let's take a look at the solution with
require
first. Its type (simplified to our case) is:— So, it takes an
IO
jar with some argument and makes it suitable for a function that lives in theServerPart
jar. We just have to adjust types a bit and create one lambda abstraction: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:Maybe
, it may returnNothing
, which means404
or the like. This is rather common a situation.IO
, it may error out, which means the database crashed. Now is the time to alert the DevOps team.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 inhappstack
specifically. Generally though, this is just a case of transforming monads into larger ones, which is done vialift
. The type oflift
(again, simplified), is:So, we can just
lift
themyFunc 1 :: IO String
value to the right monad and then compose with>>=
, as usual:As simple as that. I used the same lambda abstraction trick again, but you may as well use do-notation:
P.S. Returning to the story of large and small jars: you can put
IO
intoServerPart
precisely becauseServerPart
is also anIO
monad — it is an instance of theMonadIO
class. That means that anything you can do inIO
you can also do inServerPart
, and, besides generallift
, there is a specializedliftIO
function that you can use everywhere I usedlift
. You are likely to meet many other monads out there that are instances ofMonadIO
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 ofhappstack
meant it to be done. I'm not particularly knowledgeable abouthappstack
though, so I may be wrong.That's it. Happy hacking!