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.

Ahmad Ibrahim
  • 1,915
  • 2
  • 15
  • 32
  • 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). – chi Feb 16 '18 at 09:45
  • 1
    Cf. [*How can I parse the IO String in Haskell*](https://stackoverflow.com/q/11229854/2751851), [*transforming IO String to String*](https://stackoverflow.com/q/5948300/2751851), [*Change a function to support IO String instead of String*](https://stackoverflow.com/q/35613771/2751851). – duplode Feb 16 '18 at 13:22

1 Answers1

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!

Ignat Insarov
  • 4,660
  • 18
  • 37
  • 1
    It is perhaps worth mentioning `liftIO` (alongside, or even instead, `lift`), tying in with the OP's mention of it. – duplode Feb 16 '18 at 14:00
  • 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. – Ahmad Ibrahim Feb 16 '18 at 14:34
  • 1
    @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](http://hayoo.fh-wedel.de/). – Ignat Insarov Feb 16 '18 at 14:43
  • @Kindaro I used the `require` function and it works perfectly. Thanks – Ahmad Ibrahim Feb 16 '18 at 18:05