mirror of
https://github.com/barrucadu/dejafu.git
synced 2024-11-26 09:20:36 +03:00
116 lines
3.4 KiB
ReStructuredText
116 lines
3.4 KiB
ReStructuredText
Typeclasses
|
|
===========
|
|
|
|
We don't use the regular ``Control.Concurrent`` and
|
|
``Control.Exception`` modules, we use typeclass-generalised ones
|
|
instead from the :hackage:`concurrency` and :hackage:`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:
|
|
|
|
1. Import ``Control.Concurrent.Classy.*`` instead of
|
|
``Control.Concurrent.*``
|
|
|
|
2. Import ``Control.Monad.Catch`` instead of ``Control.Exception``
|
|
|
|
3. Change your monad type:
|
|
|
|
* ``IO a`` becomes ``MonadConc m => m a``
|
|
* ``STM a`` becomes ``MonadSTM stm => stm a``
|
|
|
|
4. Parameterise your state types by the monad:
|
|
|
|
* ``TVar`` becomes ``TVar stm``
|
|
* ``MVar`` becomes ``MVar m``
|
|
* ``IORef`` becomes ``IORef m``
|
|
|
|
5. Some functions are renamed:
|
|
|
|
* ``forkIO*`` becomes ``fork*``
|
|
* ``atomicModifyIORefCAS*`` becomes ``modifyIORefCAS*``
|
|
|
|
6. 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:
|
|
|
|
1. Given the same set of scheduling decisions, your ``IO`` code must
|
|
be deterministic [#]_
|
|
|
|
2. As dejafu can't inspect ``IO`` values, they should be kept small;
|
|
otherwise dejafu may miss buggy interleavings
|
|
|
|
3. You absolutely cannot block on the action of another thread inside
|
|
``IO``, or the test execution will just deadlock.
|
|
|
|
.. [#] This 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:
|
|
|
|
.. code-block:: haskell
|
|
|
|
{-# 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:
|
|
|
|
.. code-block:: haskell
|
|
|
|
{-# 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.
|