Question

I am using the LevelDB library and Snap framework together. I have:

main :: IO ()
main = runResourceT $ do
    db <- open "thedb" defaultOptions { createIfMissing = True }
    liftIO $ serveSnaplet defaultConfig $ initWeb db

Now in my handler, I'm unsure how to get back to MonadResource IO in order to query the database:

handleWords :: Handler App App ()
handleWords = do
    words <- uses thedb $ \db -> $ get db def "words"
    writeBS $ pack $ show words

Which gives me a: No instance for (MonadResource IO) arising from a use of 'get'

Any ideas? I feel like I'm missing something about how to properly create a monad "stack". Thanks

Was it helpful?

Solution

MonadResource/ResourceT is one way of acquiring scarce resources in a way that guarantees resources will be freed in the case of an exception. Another approach is the bracket pattern, which is supported by Snap via the bracketSnap function. You can use this to create the ResourceT context needed by LevelDB:

import qualified Control.Monad.Trans.Resource as Res
bracketSnap Res.createInternalState Res.closeInternalState $ \resState -> do
    let openAction = open "thedb" defaultOptions { createIfMissing = True }
    db <- Res.runInternalState openAction resState

This could be made simpler with some changes in Snap and leveldb:

  • Instead of only providing the open function, which presumes a MonadResource context, there could be a function which returns a Resource value. I'm making this tweak in persistent for the 2.0 release.
  • Snap could provide support for either MonadResource or the Resource monad (two separate concepts with unfortunately similar names).

OTHER TIPS

Snap doesn't need to support MonadResource or Resource for you to do this. You're doing the monad transformer composition in the wrong direction. A look at the types will help.

serveSnaplet :: Config Snap AppConfig -> SnapletInit b b -> IO ()
runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a

So you're trying to put an IO in the place that a ResourceT is expected. You should approach this the other way around. Put your open "thedb" ... call inside your application's Initializer with a liftIO. But open is a MonadResource, so you need to use the ResourceT instance to get it into an IO. It will look something like this:

app = makeSnaplet "app" "An snaplet example application." Nothing $ do
    ...
    db <- liftIO $ runResourceT $ open "thedb" defaultOptions

Then store the db handle in your App state and you can retrieve it later using Handler's MonadReader or MonadState instances.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top