bracket, (un)block and MonadIO

Iavor Diatchki
Fri, 05 Sep 2003 14:59:17 -0700


Simon Marlow wrote:
>>See the attached patch for the details.
>>This solution is maybe a bit ugly, since these methods are fairly
>>specific (liftIO' is needed to generalize block and unblock, and
>>liftIO'' is needed to generalize catchException).
>>But it does allow one to use catch/bracket/etc with monads built on
>>top of IO with monad transformers, which is quite nice:
> Thanks.  I think I would prefer to have the generalised versions of
> block/unblock/catchException separate from the IO-specific versions, and
> exported by one of the Control.Monad modules, to avoid breaking too much
> code, and to avoid wiring MonadIO in too deeply.
> Would you mind redoing the patch?
> Iavor: how do these changes sit with your redesign of the monad stuff?
well the patch does two things:
1. adds 2 new methods to the MonadIO class:
    liftIO' :: MonadIO m => (forall a. IO a -> IO a) -> m a -> m a
    liftIO'' :: MonadIO m => (forall a. IO a -> (b -> IO a) -> IO a) ->
                              m a -> (b -> m a) -> m a
2. using these methods redefines the IO exception primitives

in the "new" library there is no MonadIO class, it was generalized
to HasBaseMonad m n | m -> n, so now one can perform operations directly 
in the base monad for any base monad not just IO.  it is easy to add 
liftIO' to the HasBaseMonadClass, it essentially repeatedly applies 
'mapTrans' until the base monad is reached (and there is a bit of an 
adhoc implementtaion for continuations as they don't have mapTrans 
operation i copied that from the patch).  so now we have:
mapBase :: HasBaseMonad m n => (forall a. n a -> n a) -> m a -> m a

liftIO'' seems a bit adhoc though, and i can't quite see where to fit 
it.  suggestions on what to do with it are welcome.  to me it looks like
the lifting of 'catch' with exceptions of type b and it seems that's how 
it is used in the patch.

in the library there already are such liftings, to lift the method 
'handle' (ex catchError) of the MonadError class (it has the same type 
as 'catch').  it is unfortunate that there are so many different 'catch' 
and 'handle' methods, and for a long time i have been wondering if we 
should simply make IO an instance of the MonadError class.  then there 
is the question of what to do if one wraps an ErrorT transformer around 
IO.  in such a situation one may want to throw two different kinds of 
exceptions but with the current class structure that doesn't work, as 
the transformer's methods will "shadow" the IO ones.   there seem to be 
a few different ways to work around this, but none of them seem too great:
1. one could use a more complicated class structure as i did in my old 
monadic library, and then one can specify which error they are throwing 
or catching, i.e. there was a way to "name" different transformers of 
the same type (see for details).  this however 
was kind of clunky and there may be some language extension to make it 
easier to use, but it is probbaly not very practical as it is.
2. one can have a special way of lifting IO operations to monads that 
already have errors that re-thrpw the errros, e.g. something like:

liftIO :: (HasBaseMonad m IO, MonadError e m) =>
             (Exception -> e) -> IO a -> m a
liftIO cvt m = do x <- inBase (try m)
                   case x of
                     Left err -> raise (cvt err)
                     Right a  -> return a
any other ideas?



| Iavor S. Diatchki, Ph.D. student               |
| Department of Computer Science and Engineering |
| School of OGI at OHSU                          |
|               |