Improving MonadIO

Ben Franksen ben.franksen at
Thu Apr 22 18:02:52 EDT 2010

There was once a very inspiring message from Jules Bean on the cafe,
about "Monadic Tunneling"
( At
the time his idea perfectly served my needs, so I wrote a module to
encapsulate it. The code is below, maybe it adds another data point to the
discussion about a better MonadIO. Note that the generality is not idle, I
actually needed the transformer version in my project. (I have been
thinking about uploading this to hackage as an independent module.)

{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, EmptyDataDecls #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts, GeneralizedNewtypeDeriving
module Embed where

import Control.Concurrent
import Control.Exception
import Control.Monad.Trans.Class
import Control.Monad.Trans.Identity
import Control.Monad.Trans.Reader
import Prelude hiding (catch)

-- * Class Embed

class Embed i o where
  type Content i o
  embed :: (Content i o -> i a) -> o a
  callback :: o a -> Content i o -> i a

liftE :: (Embed i o) => i a -> o a
liftE action = embed (const action)

-- If the inner monad is IO

data Void

-- | We would like to give an instance @Embed m m@ once and for all @m at .
-- Unfortunately this does not play nicely with the generic instances below.
instance Embed IO IO where
  type Content IO IO = Void
  embed f = f undefined
  callback action _ = action

-- The constraint @Embed IO m@ is more powerful and useful than MonadIO,
-- as it allows higher-ranked liftings.

io :: (Embed IO m) => IO a -> m a
io = liftE

bracketE :: Embed IO m => m r -> (r -> m b) -> (r -> m a) -> m a
bracketE before after during =
    embed $ \x -> bracket (before' x) (\a -> after' a x) (\a -> during' a x)
    before' x = callback before x
    after' a x = callback (after a) x
    during' a x = callback (during a) x

catchE :: (Embed IO m, Exception e) => m a -> (e -> m a) -> m a
catchE action handler = embed $ \x -> catch (action' x) (\e -> handler' e x)
    action' x = callback action x
    handler' e x = callback (handler e) x

handleE :: (Embed IO m, Exception e) => (e -> m a) -> m a -> m a
handleE = flip catchE

throwE :: (Embed IO m, Exception e) => e -> m a
throwE = liftE . throwIO

forkE :: Embed IO m => m () -> m ThreadId
forkE action = embed $ \x -> forkIO (callback action x)

-- * Embedding Transformer

class MonadTrans t => Embedding t where
  type ContentT t
  embedT :: (ContentT t -> m a) -> t m a
  callbackT :: t m a -> ContentT t -> m a

defaultLift :: Embedding t => m a -> t m a
defaultLift = embedT . const

instance (Embed i o, Embedding t) => Embed i (t o) where
  type Content i (t o) = (ContentT t, Content i o)
  embed f = embedT (\x -> embed (\y -> f (x,y)))
  callback action (x,y) = callback (callbackT action x) y

instance Embedding IdentityT where
  type ContentT IdentityT = Void
  embedT f = IdentityT (f undefined)
  callbackT action _ = runIdentityT action

instance Embedding (ReaderT r) where
  type ContentT (ReaderT r) = r
  embedT = ReaderT
  callbackT = runReaderT

More information about the Libraries mailing list