forkable-monad-0.1.1: An implementation of forkIO for monad stacks.



This module defines a generic version of Control.Concurrent's forkIO, which can directly run some complex monadic actions as well as plain IO actions.

Control.Concurrent's forkIO accepts an IO computation only, and requires the caller to reconstruct the full monadic stack by hand in the new thread. In contrast, this module's forkIO runs a computation in the same monad as the parent thread, transparently transplanting the monad stack to the new thread.

For example, the following code which uses Control.Concurrent's forkIO:

 type MyMonad = ReaderT Int (StateT String IO)

 forkAndDo :: MyMonad ThreadId
 forkAndDo = do
     r <- ask
     s <- lift get
     liftIO $ forkIO $ (runStateT (runReaderT forkedDo r) s >> return ())

 forkedDo :: MyMonad ()
 forkedDo = liftIO $ putStrLn "forkedDo running"

can be reexpressed with this module's forkIO as:

 type MyMonad = ReaderT Int (StateT String IO)

 forkAndDo :: MyMonad ThreadId
 forkAndDo = forkIO forkedDo

 forkedDo :: MyMonad ()
 forkedDo = liftIO $ putStrLn "forkedDo running"

forkIO can operate on any monad that is an instance of ForkableMonad. ForkableMonad instances are defined for ReaderT and StateT, as well as IO. Here is the precise meaning of "transplant" for each of these:

  • IO requires no special work, since Control.Concurrent's forkIO already provides the "transplanting" of an IO action to a new thread.
  • ReaderT makes the parent thread's environment available for consultation in the new thread.
  • StateT makes a copy of the parent thread's state available in the new thread. The states in the two threads are not linked, so it is expected that they will diverge as each thread updates its own copy of the state.

Other standard transformers (notably WriterT, ErrorT and RWST) do not have an instance defined, because those instances can only be defined through data loss.

For example, the current output of a Writer cannot be accessed from within the monad, so the best that can be done is to create a new pristine Writer state for the new thread, and to discard all data written in that thread when the thread terminates.

If you want to use forkIO on a monad stack that includes one of these lossy monads, you will need to define the ForkableMonad instances yourself. The same goes for any custom monads you may have in the stack.

This module reexports Control.Concurrent overlayed with the generic forkIO, so you can simply change your import from Control.Concurrent to Control.Concurrent.Forkable to use this module's forkIO in your existing concurrent code.


class Monad m => ForkableMonad m whereSource


forkIO :: m a -> m ThreadIdSource

Spark off a new thread to run the monadic computation passed as the first argument, and return the ThreadId of the newly created thread.

The new thread will run the computation using the same monadic context as the parent thread.

As a convenience, this forkIO accepts a computation returning any value, not just unit. This value is discarded when the computation terminates.