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.