This module defines classes of monads that can perform multiple computations in parallel and, more importantly,
combine the results of those parallel computations.

There are two classes exported by this module, MonadParallel and MonadFork. The former is more generic, but the
latter is easier to use: when invoking any expensive computation that could be performed in parallel, simply wrap the
call in forkExec. The function immediately returns a handle to the running computation. The handle can be used to
obtain the result of the computation when needed:

do child <- forkExec expensive
otherStuff
result <- child

In this example, the computations expensive and otherStuff would be performed in parallel. When using the
MonadParallel class, both parallel computations must be specified at once:

bindM2 (\ childResult otherResult -> ...) expensive otherStuff

In either case, for best results the costs of the two computations should be roughly equal.

Any monad that is an instance of the MonadFork class is also an instance of the MonadParallel class, and the
following law should hold:

bindM2 f ma mb = do {a' <- forkExec ma; b <- mb; a <- a'; f a b}

When operating with monads free of side-effects, such as Identity or Maybe, forkExec is equivalent to return
and bindM2 is equivalent to \ f ma mb -> do {a <- ma; b <- mb; f a b} — the only difference is in the
resource utilisation. With the IO monad, on the other hand, there may be visible difference in the results because
the side effects of ma and mb may be arbitrarily reordered.

Classes

Perform two monadic computations in parallel; when they are both finished, pass the results to the function.
Apart from the possible ordering of side effects, this function is equivalent to
\f ma mb-> do {a <- ma; b <- mb; f a b}