bracket, (un)block and MonadIO
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 www.cse.ogi.edu/~diatchki 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 |
| http://www.cse.ogi.edu/~diatchki |