Generalized monadic exception handling with monad-peel

I just released the monad-peel library to Hackage. http://hackage.haskell.org/package/monad-peel MonadPeelIO is a simple class that allows lifting monadic control operations, such as those in Control.Exception and Foreign.Marshal.Alloc, through layers of monad transformers. It comes with instances for every transformer in the transformers package except ContT, and uses no Haskell extensions. class MonadTrans t => MonadTransPeel t where peel :: (Monad m, Monad n, Monad o) => t n (t m a -> m (t o a)) class MonadIO m => MonadPeelIO m where peelIO :: m (m a -> IO (m a)) I’ve included a wrapped version of Control.Exception with types generalized to all monads in MonadPeelIO, for example: catch :: (MonadPeelIO m, Exception e) => m a -> (e -> m a) -> m a catch a handler = do k <- peelIO join $ liftIO $ Control.Exception.catch (k a) (\e -> k $ handler e) I’m hoping this package can become a suitable replacement for MonadCatchIO, whose implementation of ‘finally’ was recently shown to be broken with certain monads (see “MonadCatchIO, finally and the error monad” on this list). The home page has links to a Git repository and has references to older mailing list threads providing some background: http://andersk.mit.edu/haskell/monad-peel/ Thanks to everyone who contributed to those threads and helped shape the ideas that led to this package (and if it turns out more ideas are needed, thanks for those too!). Anders

On Wed, Nov 3, 2010 at 10:59 AM, Anders Kaseorg
I just released the monad-peel library to Hackage.
This is great news! My regions library uses 'bracket' from MonadCatchIO in the runRegionT function. Due to the recent discussion on MonadCatchIO I realized that users could hack around the safety guarantees of the library by inserting an Error monad somewhere in the stack of monad transformers of a RegionT. Then they could do something like: runRegionT $ do h <- openFile "file.txt" ReadOnly throwError "die!" which then won't close h! This obviously breaks the safety guarantees of my regions library. I will update the regions library and related packages with your monad-peel. Thanks! Something else: In the soon to be released base-4.3 the block and unblock functions will be deprecated in favor of mask. It would be great if you can also add these new functions to Control.Exception.Peel: import qualified Control.Exception as CE (mask, mask_, uninterruptibleMask) #if MIN_VERSION_base(4,3,0) -- |Generalized version of 'CE.mask'. Note, any monadic side -- effects in @m@ of the \"restore\" computation will be discarded; it -- is run only for its side effects in @IO@. mask :: MonadPeelIO m => ((forall a. m a -> m a) -> m b) -> m b mask f = do k <- peelIO join $ liftIO $ CE.mask $ \restore -> k $ f $ liftIOOp_ restore -- |Generalized version of 'CE.mask_'. mask_ :: MonadPeelIO m => m a -> m a mask_ = liftIOOp_ CE.mask_ -- |Generalized version of 'CE.uninterruptibleMask'. Note, any monadic side -- effects in @m@ of the \"restore\" computation will be discarded; it -- is run only for its side effects in @IO@. uninterruptibleMask :: MonadPeelIO m => ((forall a. m a -> m a) -> m b) -> m b uninterruptibleMask f = do k <- peelIO join $ liftIO $ CE.uninterruptibleMask $ \restore -> k $ f $ liftIOOp_ restore #endif You have to review these definitions carefully; I'm not yet fully comfortable with peelIO. Finally, would you also like to add wrapped functions for alloca and friends from all the Foreign modules? Regards, Bas

On Wed, 3 Nov 2010, Bas van Dijk wrote:
Something else: In the soon to be released base-4.3 the block and unblock functions will be deprecated in favor of mask. It would be great if you can also add these new functions to Control.Exception.Peel:
Yeah, I was putting off learning how to deal with conditional compilation, but now that I’ve looked it seems easy enough. Your definitions look right to me.
Finally, would you also like to add wrapped functions for alloca and friends from all the Foreign modules?
Michael Snoyman asked me the same question [1], and my thinking is that using liftIOOp with the Foreign allocation functions is already just as easy as using liftIO with normal IO actions: liftIOOp alloca $ \ptr -> frobnicate ptr liftIOOp (allocaBytes 17) $ \ptr -> frobnicate ptr which suggests that maintaining a wrapper library would not be much of a win. The same goes with functions like withMVar. I’d be happy to hear arguments otherwise; or maybe the right answer is to clarify this in the documentation somehow? Anders [1] http://docs.yesodweb.com/blog/invertible-monads-exceptions-allocations/#comm...
participants (2)
-
Anders Kaseorg
-
Bas van Dijk