Opened 13 years ago

Last modified 4 years ago

#110 new task

Generalize types of IO actions

Reported by: Owned by: none
Priority: normal Milestone:
Version: Keywords:
Cc: Meta Owner:
State: discussion Section: N/A or multiple
Related Tickets:

Description (last modified by brianh@…)

One nice way to structure programs is to use monad transformers on top of IO. For example, one common technique is to use StateT or ReaderT to propagate custom state through IO code. This technique has two problems

1) It requires one to use 'liftIO' rather a lot, which is mildly irritating

2) It makes it very difficult to use higher-order IO combinators, such as try or bracket, which is highly irritating

This proposal involves generalizing the types of standard IO functions to make this use-case easier.

Conservative proposal:

Generalize higher order IO combinators to take a MonadIO context. For example, try and catch would then have types

try :: MonadIO m => m a -> m (Either Exception a)

catch :: MonadIO m => m a -> (Exception -> m a) - > m a

This allows one to use custom monads built on IO and still do correct exception handling, etc. without having to do lots of nasty monad unwrapping/rewrapping.

NOTE: This is not possible to implement. there is no way to 'rewind' a monad in a generic way in order to implement try or catch. - JohnMeacham?

NOTE: An alternative is to use a different version of Control.Exception that defines a typeclass as follows:

class MonadIO m => MonadException m where
    catch :: m a -> (Exception -> m a) -> m a

    block, unblock :: m a -> m a

This can be implemented for the common monad transformers, and all the other exception functions can be implemented just using the methods in the above typeclass, or using liftIO with the existing functions. A full implementation (not fully tested though) is attached. — Brian Hulley

NOTE: Even though not all uses of IO can be eliminated (eg when defining callback functions to use with FFI), the importance of having an Exception module which is not tied to concrete IO is that it would allow library writers which require block, bracket etc to use the more general MonadException in place of IO. If library writers do not do this, users of the library, which may be other libraries etc, are also tied down to concrete IO, so the longer we leave it, the more difficult it will be to "unconcretise" the code base. — Brian Hulley

More radical proposal:

Generalize the types of all standard library IO routines to take MonadIO contexts. For example,

putChar :: MonadIO m ⇒ Char → m ()

getChar :: MonadIO m ⇒ m Char

These could easily be defined in terms of lower level IO primitives:

putCharIO :: Char → IO ()

putCharIO = …

putChar c = liftIO (putChar c)

Now you can eliminate a bunch of noisy calls to liftIO in client code.

NOTE: This should be quite trivial to implement for all functions which just use IO in the return value. When IO is used in other positions it is unlikely to be possible to implement except by replacing the other occurrence of IO by MonadIOU defined by:

     class MonadIO m => MonadIOU m where
       getUnliftIO :: m (m a -> IO a)

Unfortunately only a few of the monad transformers support this operation (eg ReaderT ) but at least it's better than nothing. An alternative would be to define special typeclasses for related sets of operations if this would allow more monad transformers to be supported by the particular operations, or to add first-order api functions which a library user could use to build a specialised version of the higher order function for a specific monad. — Brian Hulley

Attachments (1)

Exception.txt (10.0 KB) - added by brianh@… 13 years ago.
Generalised wrapper for Control.Exception (rename to .hs)

Download all attachments as: .zip

Change History (5)

comment:1 Changed 13 years ago by john@…

Description: modified (diff)

comment:2 Changed 13 years ago by brianh@…

Description: modified (diff)

Changed 13 years ago by brianh@…

Attachment: Exception.txt added

Generalised wrapper for Control.Exception (rename to .hs)

comment:3 Changed 4 years ago by Herbert Valerio Riedel


moving non-milestoned many year old legacy tickets out of the way

comment:4 Changed 4 years ago by Herbert Valerio Riedel

Priority: majornormal

Set default priority (as this confuses Trac otherwise)

Note: See TracTickets for help on using tickets.