Typeclasses
We don't use the regular Control.Concurrent and Control.Exception modules,
we use typeclass-generalised ones instead from the concurrency and
exceptions packages.
Porting guide
If you want to test some existing code, you'll need to port it to the
appropriate typeclass. The typeclass is necessary, because we can't peek inside
IO and STM values, so we need to able to plug in an alternative
implementation when testing.
Fortunately, this tends to be a fairly mechanical and type-driven process:
-
Import
Control.Concurrent.Classy.*instead ofControl.Concurrent.* -
Import
Control.Monad.Catchinstead ofControl.Exception -
Change your monad type:
IO abecomesMonadConc m => m aSTM abecomesMonadSTM stm => stm a
-
Parameterise your state types by the monad:
TVarbecomesTVar stmMVarbecomesMVar mIORefbecomesIORef m
-
Some functions are renamed:
forkIO*becomesfork*atomicModifyIORefCASbecomesmodifyIORefCAS*
-
Fix the type errors
If you're lucky enough to be starting a new concurrent Haskell project, you can
just program against the MonadConc interface.
What if I really need I/O?
You can use MonadIO and liftIO with MonadConc, for instance if you need to
talk to a database (or just use some existing library which needs real I/O).
To test IO-using code, there are some rules you need to follow:
-
Given the same set of scheduling decisions, your
IOcode must be deterministic (see below). -
As dejafu can't inspect
IOvalues, they should be kept small; otherwise dejafu may miss buggy interleavings. -
You absolutely cannot block on the action of another thread inside
IO, or the test execution will just deadlock.
Deterministic IO is only essential if you're using the systematic testing (the
default). Nondeterministic IO won't break the random testing, it'll just make
things more confusing.
Deriving your own instances
There are MonadConc and MonadSTM instances for many common monad
transformers. In the simple case, where you want an instance for a newtype
wrapper around a type that has an instance, you may be able to derive it. For
example:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
data Env = Env
newtype MyMonad m a = MyMonad { runMyMonad :: ReaderT Env m a }
deriving (Functor, Applicative, Monad)
deriving instance MonadThrow m => MonadThrow (MyMonad m)
deriving instance MonadCatch m => MonadCatch (MyMonad m)
deriving instance MonadMask m => MonadMask (MyMonad m)
deriving instance MonadConc m => MonadConc (MyMonad m)
MonadSTM needs a slightly different set of classes:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
data Env = Env
newtype MyMonad m a = MyMonad { runMyMonad :: ReaderT Env m a }
deriving (Functor, Applicative, Monad, Alternative, MonadPlus)
deriving instance MonadThrow m => MonadThrow (MyMonad m)
deriving instance MonadCatch m => MonadCatch (MyMonad m)
deriving instance MonadSTM m => MonadSTM (MyMonad m)
Don't be put off by the use of UndecidableInstances, it's safe here.