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 StringtoString(usingliftIOfor 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
handlersaround 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 withIOnor withServerPart.) So, you would never convert anIO Stringto 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:
IOandServerPart. Fortunately,ServerPartbuilds uponIO, it is " larger " and can, in a sense, absorbIO: we can put someIOinto aServerPartand it will be aServerPartstill, so we may then give it tosimpleHTTP. Inhappstack, this conversion may be done viarequirefunction, but there is a more general solution as well, involving monad transformers andlift.Let's take a look at the solution with
requirefirst. Its type (simplified to our case) is:— So, it takes an
IOjar with some argument and makes it suitable for a function that lives in theServerPartjar. We just have to adjust types a bit and create one lambda abstraction:As you see, we have to make 2 modifications:
Adjust
myFuncso that it returnsMaybe, as necessitated byrequire. This is a better design becausemyFuncmay now fail in two ways:Maybe, it may returnNothing, which means404or 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
handlersso thatmyFuncis external to them. One may say more specifically: abstractmyFuncfromhandlers. This is why this syntax is called a lambda abstraction.requireis the way to deal with monads inhappstackspecifically. 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
liftthemyFunc 1 :: IO Stringvalue 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
IOintoServerPartprecisely becauseServerPartis also anIOmonad — it is an instance of theMonadIOclass. That means that anything you can do inIOyou can also do inServerPart, and, besides generallift, there is a specializedliftIOfunction that you can use everywhere I usedlift. You are likely to meet many other monads out there that are instances ofMonadIOas it's a handy way of structuring code in large applications.In your particular case, I would stick with the
requireway nevertheless because I think it's how the designers ofhappstackmeant it to be done. I'm not particularly knowledgeable abouthappstackthough, so I may be wrong.That's it. Happy hacking!