haskellhappstackhdbc

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.

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 ?

  1. I tried to convert the IO String to String (using liftIO for example).
  2. I tried to find any similar questions here.
  3. I tried to find a similar example in the Happstack Crash Course.
  4. I googled all related keywords in all different combinations.

Thanks in advance.


Solution

  • 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:

     

    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!