| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Di.Monad
Description
This module offers a monadic alternative to the “bare” logging API offered by Di.Core.
Whereas Di.Core expects you to explicitly pass around a Di object,
Di.Monad offers a MonadDi typeclass, as well functions operating on
MonadDi instances, as its public facing API.
Di.Monad exports MonadDi instances for all of the monad transformer
types in transformers
and pipes.
Nevertheless, be aware that these two APIs are compatible, so you may
choose to use the monadic API for some parts of your application, the
“bare” API for some other parts, and everything will compose and behave
as expected. Usually, runDiT is the boundary between these two APIs.
Di.Monad also provides a DiT monad transformer that has an instance
of the MonadDi typeclass and you can readily use out of the box. DiT
also implements instances for all of the typeclasses in
base,
mtl, and
exceptions.
Import this module as follows:
import Di.Core as Di (new)
import Di.Monad as Di
Synopsis
- class Monad m => MonadDi level path msg m | m -> level path msg where
- log :: MonadDi level path msg m => level -> msg -> m ()
- flush :: MonadDi level path msg m => m ()
- push :: MonadDi level path msg m => path -> m a -> m a
- filter :: MonadDi level path msg m => (level -> Seq path -> msg -> Bool) -> m a -> m a
- onException :: MonadDi level path msg m => (SomeException -> Maybe (level, Seq path, msg)) -> m a -> m a
- data DiT level path msg m a
- diT :: ((forall x. STM x -> m x) -> Di level path msg -> m a) -> DiT level path msg m a
- runDiT :: MonadIO m => Di level path msg -> DiT level path msg m a -> m a
- runDiT' :: (forall x. STM x -> m x) -> Di level path msg -> DiT level path msg m a -> m a
- hoistDiT :: (forall x. n x -> m x) -> (forall x. m x -> n x) -> DiT level path msg m a -> DiT level path msg n a
- localDiT :: (Di level path msg -> Di level' path' msg') -> DiT level' path' msg' m a -> DiT level path msg m a
MonadDi
class Monad m => MonadDi level path msg m | m -> level path msg where Source #
A MonadDi allows interacting with a Di through a
mtl-like monadic API, rather
than through the “bare” API proposed by Di.Core.
Nevertheless, be aware that these two APIs are compatible, so you may
choose to use the monadic API for some parts of your application, the
“bare” API for some other parts, and everything will compose and behave
as expected. Usually, runDiT is the boundary between these two APIs,
although not necessarily.
Semantically, is a “reader monad” that carries as its
environment a MonadDi mDi and natural transformation from STM to m.
Minimal complete definition
Methods
ask :: m (Di level path msg) Source #
ask :: (MonadTrans t, MonadDi level path msg n, m ~ t n) => m (Di level path msg) Source #
local :: (Di level path msg -> Di level path msg) -> m a -> m a Source #
Run m a with a modified Di:
local(constx)ask==purex
Identity law:
localidx == x
Distributive law:
localf.localg ==local(f.g)
Idempotence law:
localf (pure())>>x == x
natSTM :: STM a -> m a Source #
Natural transformation from STM to m.
Notice that it is not necessary for this natural transformation to be a
monad morphism as well. That is, atomically is acceptable.
natSTM :: (MonadTrans t, MonadDi level path msg n, m ~ t n) => STM a -> m a Source #
Natural transformation from STM to m.
Notice that it is not necessary for this natural transformation to be a
monad morphism as well. That is, atomically is acceptable.
Instances
| MonadDi level path msg m => MonadDi level path msg (ListT m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (MaybeT m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (SelectT r m) Source # | |
| (Monoid w, MonadDi level path msg m) => MonadDi level path msg (AccumT w m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (IdentityT m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (ExceptT e m) Source # | |
| (Monoid w, MonadDi level path msg m) => MonadDi level path msg (WriterT w m) Source # | |
| (Monoid w, MonadDi level path msg m) => MonadDi level path msg (WriterT w m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (StateT s m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (StateT s m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (ContT r m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (ReaderT r m) Source # | |
| (Monoid w, MonadDi level path msg m) => MonadDi level path msg (RWST r w s m) Source # | |
| (Monoid w, MonadDi level path msg m) => MonadDi level path msg (RWST r w s m) Source # | |
| Monad m => MonadDi level path msg (DiT level path msg m) Source # | |
| MonadDi level path msg m => MonadDi level path msg (Proxy a' a b' b m) Source # | |
log :: MonadDi level path msg m => level -> msg -> m () Source #
Log a message with the given importance level.
This function returns immediately after queing the message for logging. The
actual printing of the log message will happen in a different thread,
asynchronously. If you want to explicitly wait for the message to be logged,
then call flush afterwards.
Log messages are rendered in FIFO order, and their timestamp records the
time when this log function was called, rather than the time when the log
message is printed in the future.
Note regarding exceptions: Any exception thrown by natSTM will be
thrown here. Synchronous exceptions that happen due to failures in the
actual committing of the log message are handled by attempting to log the
message to stderr as a fallback if possible. Asynchronous exceptions
happening as part of the committing process will be thrown in a different
thread, and are not not explicitly handled. Pure exceptions originating
from the filter function will be thrown here. In practical terms, this
means that unless you know what you are doing, you should just call log'
without worrying about it ever throwing exceptions.
flush :: MonadDi level path msg m => m () Source #
Block until all messages being logged have finished processing.
Manually calling flush is not usually necessary because all log messages
are processed as soon as possible, and with ensures that no log message is
left unprocessed. However, the actual printing of log messages happens
asynchronously, meaning there might be log messages still waiting to be
processed. A call to flush will block until all pending log messages have
been processed.
push :: MonadDi level path msg m => path -> m a -> m a Source #
Run the given action under a deeper path.
Arguments
| :: MonadDi level path msg m | |
| => (level -> Seq path -> msg -> Bool) | Whether a particular log entry with the given The given |
| -> m a | |
| -> m a |
Require that any logged messages within the given action satisfy the given predicate in order to be accepted for processing. Logged messages that don't satisfy the predicate will be silently discarded.
Identity:
filter(\_ _ _ ->True) ==id
Composition:
filter(\l ps m -> f l ps m&&g l ps m) ==filterf .filterg
Commutativity:
filterf .filterg ==filterg .filterf
onException :: MonadDi level path msg m => (SomeException -> Maybe (level, Seq path, msg)) -> m a -> m a Source #
Within the passed given m a, exceptions thrown with throwM could
could be logged as a msg with a particular level if both the passed in
function returns Just, and filter so allows it afterwards.
If the given function returns Nothing, then no logging is performed.
The returned will extend the Seq pathpath at the throw call site
before sending the log. The leftmost path is closest to the root.
Composition:
onExceptionf .onExceptiong ==onException(g e *> f e)
Notice that the level, paths and msg resulting from g are discarded,
yet its policy regarding whether to log or not is preserved in the same way
as filter. That is, onException can't accept an exception already
rejected by a previous use of onException, but it can reject a previously
accepted one.
DiT
data DiT level path msg m a Source #
A is a “reader monad” that carries as its
environment a DiT level path msg m and natural transformation from Di level path msgSTM to
m.
Instances
| Monad m => MonadDi level path msg (DiT level path msg m) Source # | |
| MonadWriter w m => MonadWriter w (DiT level path msg m) Source # | |
| MonadState s m => MonadState s (DiT level path msg m) Source # | |
| MonadReader r m => MonadReader r (DiT level path msg m) Source # | |
| MonadTrans (DiT level path msg) Source # | |
| Monad m => Monad (DiT level path msg m) Source # | |
| Functor m => Functor (DiT level path msg m) Source # | |
| MonadFix m => MonadFix (DiT level path msg m) Source # | |
| MonadFail m => MonadFail (DiT level path msg m) Source # | |
| Applicative m => Applicative (DiT level path msg m) Source # | |
Defined in Di.Monad Methods pure :: a -> DiT level path msg m a # (<*>) :: DiT level path msg m (a -> b) -> DiT level path msg m a -> DiT level path msg m b # liftA2 :: (a -> b -> c) -> DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m c # (*>) :: DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m b # (<*) :: DiT level path msg m a -> DiT level path msg m b -> DiT level path msg m a # | |
| MonadZip m => MonadZip (DiT level path msg m) Source # | |
Defined in Di.Monad | |
| MonadIO m => MonadIO (DiT level path msg m) Source # | |
| Alternative m => Alternative (DiT level path msg m) Source # | |
| MonadPlus m => MonadPlus (DiT level path msg m) Source # | |
| MonadMask m => MonadMask (DiT level path msg m) Source # | |
Defined in Di.Monad Methods mask :: ((forall a. DiT level path msg m a -> DiT level path msg m a) -> DiT level path msg m b) -> DiT level path msg m b # uninterruptibleMask :: ((forall a. DiT level path msg m a -> DiT level path msg m a) -> DiT level path msg m b) -> DiT level path msg m b # generalBracket :: DiT level path msg m a -> (a -> ExitCase b -> DiT level path msg m c) -> (a -> DiT level path msg m b) -> DiT level path msg m (b, c) # | |
| MonadThrow (DiT level path msg STM) Source # | This instance doesn't log exceptions before throwing them. |
| MonadMask m => MonadThrow (DiT level path msg m) Source # | Throw an Note: Any new exception that might happen as part of the logging process is silenced, so that the originally thrown exception is the one that has precendence. Note that the |
| (MonadThrow (DiT level path msg m), MonadCatch m) => MonadCatch (DiT level path msg m) Source # | |
| MonadCont m => MonadCont (DiT level path msg m) Source # | |
Run a DiT.
forall di.runDiTdi (diT(\nat' di' -> pure (nat', di'))) ==pure(natSTM, di)
This is like runDiT', but specialized to run with an underlying MonadIO.
runDiT==runDiT'(liftIO.atomically)
Please notice that runDiT doesn't perform a flush on the given Di
before returning. You are responsible for doing that (or, more likely,
new will do it for you).
Also, notice that runDiT is a monad morphism from to DiT mm.
Run a DiT.
forall nat di.runDiT'nat di (diT(\nat' di' -> pure (nat', di'))) ==pure(nat, di)
runDiT' is like runDiT. However it doesn't require a MonadIO
constraint. Instead, it takes the natural transformation that will be used
by natSTM as an argument.
First, this allows any monad that wraps IO without necessarily having a
MonadIO instance to work with MonadDi. For example:
newtype Foo = Foo (IOa) deriving (Functor,Applicative,Monad)runDiT'(Foo .atomically) ::Dilevel path msg ->DiTlevel path msg Foo a -> Foo a
Second, this allows m to be STM itself:
runDiT'id::Dilevel path msg ->DiTlevel path msgSTMa ->STMa
The semantics of logging from within STM are those of any other STM
transaction: That is, a log message is commited only once to the outside
world if and when the STM transaction succeeds. That is, the following
example will only ever commit the log containing ly and my, and not
the one containing lx and mx.
atomically$runDiT'id$ do (logidlx mx >>liftretry) <|> (logidly my)
Of course, runDiT' works as well if you decide to wrap STM with your own
monad type:
newtype Bar = Bar (STMa) deriving (Functor,Applicative,Monad)runDiT'Bar ::Dilevel path msg ->DiTlevel path msg Bar a -> Bar a
Additionally, notice that runDiT' itself is a monad morphism from
to DiT level path msg mm which doesn't perform any side effects
of its own. Particularly, the given Di remains unaffected. So you can use
it as many times you want.
forall f di x.runDiT'f di (liftx) == x
Please notice that runDiT doesn't perform a flush on the given Di
before returning. You are responsible for doing that (or, more likely,
new will do it for you).
Arguments
| :: (forall x. n x -> m x) | Natural transformation from |
| -> (forall x. m x -> n x) | Monad morphism from |
| -> DiT level path msg m a -> DiT level path msg n a |
Lift a monad morphism from m to n to a monad morphism from
to DiT level path msg m.DiT n
Notice that DiT itself is not a functor in the category of monads,
so it can't be an instance of MFunctor from the
mmorph package.
However, it becomes one if you pair it with a natural transformation
. That is:nat :: forall x. n x -> m x
forall nat. such thatnatis a natural transformationhoistDiTnat ==hoist
In practical terms, it means that most times you can “hoist” a DiT
anyway, just not through hoist.
Arguments
| :: (Di level path msg -> Di level' path' msg') | |
| -> DiT level' path' msg' m a | |
| -> DiT level path msg m a |
localDiT(constx)ask==purex
Notice that, contrary to local, this allows changing the type of Di,
which means that you can use localDiT with contralevel,
contrapath or contramsg to change the types of level,
path, or message you DiT works with.
localDiT(contralevel(f :: level -> level')) ::DiTlevel' path msg m a ->DiTlevel path msg m a
localDiT(contrapath(f :: path -> path')) ::DiTlevel path' msg m a ->DiTlevel path msg m a
localDiT(contramsg(f :: msg -> msg')) ::DiTlevel path msg' m a ->DiTlevel path msg m a
Identity law:
localDiTidx == x
Distributive law:
localDiTf.localDiTg ==localDiT(f.g)
Idempotence law:
localDiTf (pure())>>x == x