Safe Haskell | Safe |
---|---|
Language | Haskell2010 |
A monad morphism is a natural transformation:
morph :: forall a . m a -> n a
... that obeys the following two laws:
morph $ do x <- m = do x <- morph m f x morph (f x) morph (return x) = return x
... which are equivalent to the following two functor laws:
morph . (f >=> g) = morph . f >=> morph . g morph . return = return
Examples of monad morphisms include:
-
lift
(fromMonadTrans
) -
squash
(See below) -
hoist
ff
is a monad morphism -
(f . g)
, iff
andg
are both monad morphisms -
id
Monad morphisms commonly arise when manipulating existing monad transformer
code for compatibility purposes. The
MFunctor
,
MonadTrans
, and
MMonad
classes define standard ways to change monad transformer stacks:
Synopsis
- class MFunctor t where
- generalize :: Monad m => Identity a -> m a
- class ( MFunctor t, MonadTrans t) => MMonad t where
- class MonadTrans (t :: ( Type -> Type ) -> Type -> Type ) where
- squash :: ( Monad m, MMonad t) => t (t m) a -> t m a
- (>|>) :: ( Monad m3, MMonad t) => ( forall a. m1 a -> t m2 a) -> ( forall b. m2 b -> t m3 b) -> m1 c -> t m3 c
- (<|<) :: ( Monad m3, MMonad t) => ( forall b. m2 b -> t m3 b) -> ( forall a. m1 a -> t m2 a) -> m1 c -> t m3 c
- (=<|) :: ( Monad n, MMonad t) => ( forall a. m a -> t n a) -> t m b -> t n b
- (|>=) :: ( Monad n, MMonad t) => t m b -> ( forall a. m a -> t n a) -> t n b
Functors over Monads
class MFunctor t where Source #
A functor in the category of monads, using
hoist
as the analog of
fmap
:
hoist (f . g) = hoist f . hoist g hoist id = id
hoist :: Monad m => ( forall a. m a -> n a) -> t m b -> t n b Source #
Lift a monad morphism from
m
to
n
into a monad morphism from
(t m)
to
(t n)
The first argument to
hoist
must be a monad morphism, even though the
type system does not enforce this
Instances
generalize :: Monad m => Identity a -> m a Source #
A function that
generalize
s the
Identity
base monad to be any monad.
Monads over Monads
class ( MFunctor t, MonadTrans t) => MMonad t where Source #
A monad in the category of monads, using
lift
from
MonadTrans
as the
analog of
return
and
embed
as the analog of (
=<<
):
embed lift = id embed f (lift m) = f m embed g (embed f t) = embed (\m -> embed g (f m)) t
class MonadTrans (t :: ( Type -> Type ) -> Type -> Type ) where Source #
The class of monad transformers. Instances should satisfy the
following laws, which state that
lift
is a monad transformation:
lift :: Monad m => m a -> t m a Source #
Lift a computation from the argument monad to the constructed monad.
Instances
MonadTrans ListT | |
MonadTrans MaybeT | |
MonadTrans ( IdentityT :: ( Type -> Type ) -> Type -> Type ) | |
MonadTrans ( ErrorT e) | |
MonadTrans ( ExceptT e) | |
MonadTrans ( ReaderT r) | |
MonadTrans ( StateT s) | |
MonadTrans ( StateT s) | |
Monoid w => MonadTrans ( WriterT w) | |
Monoid w => MonadTrans ( WriterT w) | |
MonadTrans ( ContT r) | |
( MFunctor f, MonadTrans f, MonadTrans g) => MonadTrans ( ComposeT f g) Source # | |
Monoid w => MonadTrans ( RWST r w s) | |
Monoid w => MonadTrans ( RWST r w s) | |
(>|>) :: ( Monad m3, MMonad t) => ( forall a. m1 a -> t m2 a) -> ( forall b. m2 b -> t m3 b) -> m1 c -> t m3 c infixr 2 Source #
(<|<) :: ( Monad m3, MMonad t) => ( forall b. m2 b -> t m3 b) -> ( forall a. m1 a -> t m2 a) -> m1 c -> t m3 c infixl 2 Source #
Tutorial
Monad morphisms solve the common problem of fixing monadic code after the fact without modifying the original source code or type signatures. The following sections illustrate various examples of transparently modifying existing functions.
Generalizing base monads
Imagine that some library provided the following
State
code:
import Control.Monad.Trans.State tick :: State Int () tick = modify (+1)
... but we would prefer to reuse
tick
within a larger
(
block in order to mix in
StateT
Int
IO
)
IO
actions.
We could patch the original library to generalize
tick
's type signature:
tick :: (Monad m) => StateT Int m ()
... but we would prefer not to fork upstream code if possible. How could
we generalize
tick
's type without modifying the original code?
We can solve this if we realize that
State
is a type synonym for
StateT
with an
Identity
base monad:
type State s = StateT s Identity
... which means that
tick
's true type is actually:
tick :: StateT Int Identity ()
Now all we need is a function that
generalize
s the
Identity
base monad
to be any monad:
import Data.Functor.Identity generalize :: (Monad m) => Identity a -> m a generalize m = return (runIdentity m)
... which we can
hoist
to change
tick
's base monad:
hoist :: (Monad m, MFunctor t) => (forall a . m a -> n a) -> t m b -> t n b hoist generalize :: (Monad m, MFunctor t) => t Identity b -> t m b hoist generalize tick :: (Monad m) => StateT Int m ()
This lets us mix
tick
alongside
IO
using
lift
:
import Control.Monad.Morph import Control.Monad.Trans.Class tock :: StateT Int IO () tock = do hoist generalize tick :: (Monad m) => StateT Int m () lift $ putStrLn "Tock!" :: (MonadTrans t) => t IO ()
>>>
runStateT tock 0
Tock! ((), 1)
Monad morphisms
Notice that
generalize
is a monad morphism, and the following two proofs
show how
generalize
satisfies the monad morphism laws. You can refer to
these proofs as an example for how to prove a function obeys the monad
morphism laws:
generalize (return x) -- Definition of 'return' for the Identity monad = generalize (Identity x) -- Definition of 'generalize' = return (runIdentity (Identity x)) -- runIdentity (Identity x) = x = return x
generalize $ do x <- m f x -- Definition of (>>=) for the Identity monad = generalize (f (runIdentity m)) -- Definition of 'generalize' = return (runIdentity (f (runIdentity m))) -- Monad law: Left identity = do x <- return (runIdentity m) return (runIdentity (f x)) -- Definition of 'generalize' in reverse = do x <- generalize m generalize (f x)
Mixing diverse transformers
You can combine
hoist
and
lift
to insert arbitrary layers anywhere
within a monad transformer stack. This comes in handy when interleaving two
diverse stacks.
For example, we might want to combine the following
save
function:
import Control.Monad.Trans.Writer -- i.e. :: StateT Int (WriterT [Int] Identity) () save :: StateT Int (Writer [Int]) () save = do n <- get lift $ tell [n]
... with our previous
tock
function:
tock :: StateT Int IO ()
However,
save
and
tock
differ in two ways:
We can mix the two by inserting a
WriterT
layer for
tock
and
generalizing
save
's base monad:
import Control.Monad program :: StateT Int (WriterT [Int] IO) () program = replicateM_ 4 $ do hoist lift tock :: (MonadTrans t) => StateT Int (t IO) () hoist (hoist generalize) save :: (Monad m) => StateT Int (WriterT [Int] m ) ()
>>>
execWriterT (runStateT program 0)
Tock! Tock! Tock! Tock! [1,2,3,4]
Embedding transformers
Suppose we decided to
check
all
IOException
s using a combination of
try
and
ErrorT
:
import Control.Exception import Control.Monad.Trans.Class import Control.Monad.Trans.Error check :: IO a -> ErrorT IOException IO a check io = ErrorT (try io)
... but then we forget to use
check
in one spot, mistakenly using
lift
instead:
program :: ErrorT IOException IO () program = do str <- lift $ readFile "test.txt" check $ putStr str
>>>
runErrorT program
*** Exception: test.txt: openFile: does not exist (No such file or directory)
How could we go back and fix
program
without modifying its source code?
Well,
check
is a monad morphism, but we can't
hoist
it to modify the
base monad because then we get two
ErrorT
layers instead of one:
hoist check :: (MFunctor t) => t IO a -> t (ErrorT IOException IO) a hoist check program :: ErrorT IOException (ErrorT IOException IO) ()
We'd prefer to
embed
all newly generated exceptions in the existing
ErrorT
layer:
embed check :: ErrorT IOException IO a -> ErrorT IOException IO a embed check program :: ErrorT IOException IO ()
This correctly checks the exceptions that slipped through the cracks:
>>>
import Control.Monad.Morph
>>>
runErrorT (embed check program)
Left test.txt: openFile: does not exist (No such file or directory)