mongodbhaskellyesodhaskell-prelude

Difference between prelude and yesod with MongoDB


I'm trying to get my MongoDB working in Haskell. I was using this tutorial as a starting point. When I do these commands in prelude, I get a list of collections:

pipe <- runIOE $ connect $ Host "XXXXX.mongolab.com" $ PortNumber 33317
access pipe master <databaseName> $ auth <username> <password>
access pipe master <databaseName> allCollections

However, when I try to put that in Yesod Handler, it's not compiling. I have

getActivityR :: Handler Import.Value
getActivityR = do
    pipe <- runIOE $ connect $ Host "XXXXX.mongolab.com" $ PortNumber 33317
    access pipe master <databaseName> $ auth <username> <password>
    access pipe master <databaseName> allCollections
    returnJson $ object ["activity" .= ("abc" :: Text)]

The returnJson is really just there to let me know I completed the method. Eventually it will return the list of activities.

The error I'm getting is:

Couldn't match type `IO' with `HandlerT App IO'

    Expected type: HandlerT App IO Pipe

      Actual type: IO Pipe

    In a stmt of a 'do' block:

      pipe <- runIOE

              $ connect $ Host "XXXXXX.mongolab.com" $ PortNumber 33317

So what is the difference between Prelude/GHCi and my Yesod code being built by Cabal?


Solution

  • The problem is that GHCi runs your code in the IO monad, while your function is in the HandlerT App IO monad. But HandlerT is a monad transformer over the IO monad, so you can use lift to "promote" your IO action to a HandlerT App IO action. The type of lift is:

    lift :: (MonadTrans t, Monad m) => m a -> t m a
    

    In your case, t has to be HandlerT App and m has to be IO. So the correct code looks like this:

    getActivityR :: Handler Import.Value
    getActivityR = do
        pipe <- lift $ runIOE $ connect $ Host "XXXXX.mongolab.com" $ PortNumber 33317
        access pipe master <databaseName> $ auth <username> <password>
        access pipe master <databaseName> allCollections
        returnJson $ object ["activity" .= ("abc" :: Text)]
    

    I don't know what your access is, so I can't tell whether you need the lift there too or not.

    For your special case, there is also liftIO which is like lift, but lifts straight to IO, not only to the next "layer" in your stack of monad transformers, so you should use it:

    pipe <- liftIO $ runIOE $ connect $ Host "XXXXX.mongolab.com" $ PortNumber 33317
    

    If you want to know more about this topic you should look for "monad transformers."