1

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.

2
  • 2
    I'd recommend to read a monad tutorial. There are many in the web. IO String vs String is one of the classic "common issues" in Haskell. You are trying to do pretty advanced stuff (Happstack) which involves monads (and possibly monad transformers). Grasping monads will not provide the immediate solution to your specific problems, but will tell you that certain approaches like trying to convert IO String to String can not work (in a sense, a main design feature of the IO monad is precisely to prevent such conversion). Commented Feb 16, 2018 at 9:45
  • 1

1 Answer 1

6

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!

Sign up to request clarification or add additional context in comments.

4 Comments

It is perhaps worth mentioning liftIO (alongside, or even instead, lift), tying in with the OP's mention of it.
Thank you so much for your thorough answer.. I already tried to use lift before but I get this error Variable not in scope: lift :: IO String -> ServerPartT IO (ServerPartT IO Response) Perhaps you meant 'liftM' (imported from Control.Monad), and I couldn't find which package or interface I missed.
@asp You're welcome! The module hierarchy and the package interdependencies in the case of monads and monad transformers are more complicated than they could be, due to historical reasons, but one can usually find what one seeks in hayoo.
@Kindaro I used the require function and it works perfectly. Thanks

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.