{-# LANGUAGE CPP                  #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE Rank2Types           #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

{- |
Copyright:  (c) 2018-2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

Implements core data types and combinators for logging actions.
-}

module Colog.Core.Action
       ( -- * Core type and instances
         LogAction (..)
       , (<&)
       , (&>)

         -- * 'Semigroup' combinators
       , foldActions

         -- * Contravariant combinators
         -- $contravariant
       , cfilter
       , cfilterM
       , cmap
       , (>$<)
       , cmapMaybe
       , cmapMaybeM
       , (Colog.Core.Action.>$)
       , cmapM

         -- * Divisible combinators
         -- $divisible
       , divide
       , divideM
       , conquer
       , (>*<)
       , (>*)
       , (*<)

         -- * Decidable combinators
         -- $decidable
       , lose
       , choose
       , chooseM
       , (>|<)

         -- * Comonadic combinators
         -- $comonad
       , extract
       , extend
       , (=>>)
       , (<<=)
       , duplicate
       , multiplicate
       , separate

         -- * Higher-order combinators
       , hoistLogAction
       ) where

import Control.Monad (when, (<=<), (>=>))
import Data.Coerce (coerce)
import Data.Foldable (fold, for_, traverse_)
import Data.Kind (Constraint)
import Data.List.NonEmpty (NonEmpty (..))
import Data.Monoid (Monoid (..))
import Data.Semigroup (Semigroup (..), stimesMonoid)
import Data.Void (Void, absurd)
import GHC.TypeLits (ErrorMessage (..), TypeError)

#if MIN_VERSION_base(4,12,0)
import qualified Data.Functor.Contravariant as Contravariant
#endif

{- $setup
>>> import Colog.Core.IO
-}

----------------------------------------------------------------------------
-- Core data type with instances
----------------------------------------------------------------------------

{- | Polymorphic and very general logging action type.

* @__msg__@ type variables is an input for logger. It can be 'Text' or custom
logging messsage with different fields that you want to format in future.

* @__m__@ type variable is for monadic action inside which logging is happening. It
can be either 'IO' or some custom pure monad.

Key design point here is that 'LogAction' is:

* 'Semigroup'
* 'Monoid'
* 'Data.Functor.Contravariant.Contravariant'
* 'Data.Functor.Contravariant.Divisible.Divisible'
* 'Data.Functor.Contravariant.Divisible.Decidable'
* 'Control.Comonad.Comonad'
-}
newtype LogAction m msg = LogAction
    { LogAction m msg -> msg -> m ()
unLogAction :: msg -> m ()
    }

{- | This instance allows you to join multiple logging actions into single one.

For example, if you have two actions like these:

@
logToStdout :: 'LogAction' IO String  -- outputs String to terminal
logToFile   :: 'LogAction' IO String  -- appends String to some file
@

You can create new 'LogAction' that perform both actions one after another using 'Semigroup':

@
logToBoth :: 'LogAction' IO String  -- outputs String to both terminal and some file
logToBoth = logToStdout <> logToFile
@
-}
instance Applicative m => Semigroup (LogAction m a) where
    (<>) :: LogAction m a -> LogAction m a -> LogAction m a
    LogAction action1 :: a -> m ()
action1 <> :: LogAction m a -> LogAction m a -> LogAction m a
<> LogAction action2 :: a -> m ()
action2 = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \a :: a
a -> a -> m ()
action1 a
a m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> a -> m ()
action2 a
a
    {-# INLINE (<>) #-}

    sconcat :: NonEmpty (LogAction m a) -> LogAction m a
    sconcat :: NonEmpty (LogAction m a) -> LogAction m a
sconcat = NonEmpty (LogAction m a) -> LogAction m a
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Applicative m) =>
t (LogAction m a) -> LogAction m a
foldActions
    {-# INLINE sconcat #-}

    stimes :: Integral b => b -> LogAction m a -> LogAction m a
    stimes :: b -> LogAction m a -> LogAction m a
stimes = b -> LogAction m a -> LogAction m a
forall b a. (Integral b, Monoid a) => b -> a -> a
stimesMonoid
    {-# INLINE stimes #-}

instance Applicative m => Monoid (LogAction m a) where
    mappend :: LogAction m a -> LogAction m a -> LogAction m a
    mappend :: LogAction m a -> LogAction m a -> LogAction m a
mappend = LogAction m a -> LogAction m a -> LogAction m a
forall a. Semigroup a => a -> a -> a
(<>)
    {-# INLINE mappend #-}

    mempty :: LogAction m a
    mempty :: LogAction m a
mempty = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \_ -> () -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
    {-# INLINE mempty #-}

    mconcat :: [LogAction m a] -> LogAction m a
    mconcat :: [LogAction m a] -> LogAction m a
mconcat = [LogAction m a] -> LogAction m a
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Applicative m) =>
t (LogAction m a) -> LogAction m a
foldActions
    {-# INLINE mconcat #-}

#if MIN_VERSION_base(4,12,0)
instance Contravariant.Contravariant (LogAction m) where
    contramap :: (a -> b) -> LogAction m b -> LogAction m a
    contramap :: (a -> b) -> LogAction m b -> LogAction m a
contramap = (a -> b) -> LogAction m b -> LogAction m a
forall a b (m :: * -> *).
(a -> b) -> LogAction m b -> LogAction m a
cmap
    {-# INLINE contramap #-}

    (>$) :: b -> LogAction m b -> LogAction m a
    >$ :: b -> LogAction m b -> LogAction m a
(>$) = b -> LogAction m b -> LogAction m a
forall b (m :: * -> *) a. b -> LogAction m b -> LogAction m a
(Colog.Core.Action.>$)
    {-# INLINE (>$) #-}
#endif

-- | For tracking usage of unrepresentable class instances of 'LogAction'.
type family UnrepresentableClass :: Constraint
  where
    UnrepresentableClass = TypeError
        ( 'Text "'LogAction' cannot have a 'Functor' instance by design."
        ':$$: 'Text "However, you've attempted to use this instance."
#if MIN_VERSION_base(4,12,0)
        ':$$: 'Text ""
        ':$$: 'Text "Probably you meant 'Contravariant' class instance with the following methods:"
        ':$$: 'Text "  * contramap :: (a -> b) -> LogAction m b -> LogAction m a"
        ':$$: 'Text "  * (>$) :: b -> LogAction m b -> LogAction m a"
#endif
        )

{- | ⚠️__CAUTION__⚠️ This instance is for custom error display only.

'LogAction' is not supposed to have 'Functor' instance by design.

In case it is used by mistake, the user  will see the following:

#if MIN_VERSION_base(4,12,0)

>>> fmap show logStringStdout
...
... 'LogAction' cannot have a 'Functor' instance by design.
      However, you've attempted to use this instance.
...
      Probably you meant 'Contravariant' class instance with the following methods:
        * contramap :: (a -> b) -> LogAction m b -> LogAction m a
        * (>$) :: b -> LogAction m b -> LogAction m a
...


#else

>>> fmap show logStringStdout
...
... 'LogAction' cannot have a 'Functor' instance by design.
      However, you've attempted to use this instance.
...

#endif

@since 0.2.1.0
-}
instance UnrepresentableClass => Functor (LogAction m) where
    fmap :: (a -> b) -> LogAction m a -> LogAction m b
    fmap :: (a -> b) -> LogAction m a -> LogAction m b
fmap _ _ = [Char] -> LogAction m b
forall a. HasCallStack => [Char] -> a
error "Unreachable Functor instance of LogAction"

    (<$) :: a -> LogAction m b -> LogAction m a
    _ <$ :: a -> LogAction m b -> LogAction m a
<$  _ = [Char] -> LogAction m a
forall a. HasCallStack => [Char] -> a
error "Unreachable Functor instance of LogAction"


{- | Operator version of 'unLogAction'. Note that because of the types, something like:

@
action <& msg1 <& msg2
@

doesn't make sense. Instead you want:

@
action <& msg1 \>\> action <& msg2
@

In addition, because '<&' has higher precedence than the other operators in this
module, the following:

@
f >$< action <& msg
@

is equivalent to:

@
(f >$< action) <& msg
@
-}
infix 5 <&
(<&) :: LogAction m msg -> msg -> m ()
<& :: LogAction m msg -> msg -> m ()
(<&) = LogAction m msg -> msg -> m ()
forall a b. Coercible a b => a -> b
coerce
{-# INLINE (<&) #-}

{- | A flipped version of '<&'.

It shares the same precedence as '<&', so make sure to surround lower precedence
operators in parentheses:

@
msg &> (f >$< action)
@
-}
infix 5 &>
(&>) :: msg -> LogAction m msg -> m ()
&> :: msg -> LogAction m msg -> m ()
(&>) = (LogAction m msg -> msg -> m ()) -> msg -> LogAction m msg -> m ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip LogAction m msg -> msg -> m ()
forall (m :: * -> *) msg. LogAction m msg -> msg -> m ()
(<&)
{-# INLINE (&>) #-}

{- | Joins some 'Foldable' of 'LogAction's into single 'LogAction' using
'Semigroup' instance for 'LogAction'. This is basically specialized version of
'Data.Foldable.fold' function.
-}
foldActions :: (Foldable t, Applicative m) => t (LogAction m a) -> LogAction m a
foldActions :: t (LogAction m a) -> LogAction m a
foldActions actions :: t (LogAction m a)
actions = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \a :: a
a -> t (LogAction m a) -> (LogAction m a -> m ()) -> m ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ t (LogAction m a)
actions ((LogAction m a -> m ()) -> m ())
-> (LogAction m a -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \(LogAction action :: a -> m ()
action) -> a -> m ()
action a
a
{-# INLINE foldActions #-}
{-# SPECIALIZE foldActions :: Applicative m => [LogAction m a]          -> LogAction m a #-}
{-# SPECIALIZE foldActions :: Applicative m => NonEmpty (LogAction m a) -> LogAction m a #-}

----------------------------------------------------------------------------
-- Contravariant combinators
----------------------------------------------------------------------------

{- $contravariant

Combinators that implement interface in the spirit of the following typeclass:

@
__class__ Contravariant f __where__
    contramap :: (a -> b) -> f b -> f a
@
-}

{- | Takes predicate and performs given logging action only if predicate returns
'True' on input logging message.
-}
cfilter :: Applicative m => (msg -> Bool) -> LogAction m msg -> LogAction m msg
cfilter :: (msg -> Bool) -> LogAction m msg -> LogAction m msg
cfilter predicate :: msg -> Bool
predicate (LogAction action :: msg -> m ()
action) = (msg -> m ()) -> LogAction m msg
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((msg -> m ()) -> LogAction m msg)
-> (msg -> m ()) -> LogAction m msg
forall a b. (a -> b) -> a -> b
$ \a :: msg
a -> Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (msg -> Bool
predicate msg
a) (msg -> m ()
action msg
a)
{-# INLINE cfilter #-}

{- | Performs the given logging action only if satisfies the monadic
predicate. Let's say you want to only to see logs that happened on
weekends.

@
isWeekendM :: MessageWithTimestamp -> IO Bool
@

And use it with 'cfilterM' like this

@
logMessageAction :: 'LogAction' m MessageWithTimestamp

logWeekendAction :: 'LogAction' m MessageWithTimestamp
logWeekendAction = cfilterM isWeekendM logMessageAction
@

@since 0.2.1.0
-}
cfilterM :: Monad m => (msg -> m Bool) -> LogAction m msg -> LogAction m msg
cfilterM :: (msg -> m Bool) -> LogAction m msg -> LogAction m msg
cfilterM predicateM :: msg -> m Bool
predicateM (LogAction action :: msg -> m ()
action) =
    (msg -> m ()) -> LogAction m msg
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((msg -> m ()) -> LogAction m msg)
-> (msg -> m ()) -> LogAction m msg
forall a b. (a -> b) -> a -> b
$ \a :: msg
a -> msg -> m Bool
predicateM msg
a m Bool -> (Bool -> m ()) -> m ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \b :: Bool
b -> Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
b (msg -> m ()
action msg
a)
{-# INLINE cfilterM #-}

{- | This combinator is @contramap@ from contravariant functor. It is useful
when you have something like

@
__data__ LogRecord = LR
    { lrName    :: LoggerName
    , lrMessage :: Text
    }
@

and you need to provide 'LogAction' which consumes @LogRecord@

@
logRecordAction :: 'LogAction' m LogRecord
@

when you only have action that consumes 'Text'

@
logTextAction :: 'LogAction' m Text
@

With 'cmap' you can do the following:

@
logRecordAction :: 'LogAction' m LogRecord
logRecordAction = 'cmap' lrMesssage logTextAction
@

This action will print only @lrMessage@ from @LogRecord@. But if you have
formatting function like this:

@
formatLogRecord :: LogRecord -> Text
@

you can apply it instead of @lrMessage@ to log formatted @LogRecord@ as 'Text'.
-}
cmap :: (a -> b) -> LogAction m b -> LogAction m a
cmap :: (a -> b) -> LogAction m b -> LogAction m a
cmap f :: a -> b
f (LogAction action :: b -> m ()
action) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (b -> m ()
action (b -> m ()) -> (a -> b) -> a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
f)
{-# INLINE cmap #-}

{- | Operator version of 'cmap'.

>>> 1 &> (show >$< logStringStdout)
1
-}
infixr 3 >$<
(>$<) :: (a -> b) -> LogAction m b -> LogAction m a
>$< :: (a -> b) -> LogAction m b -> LogAction m a
(>$<) = (a -> b) -> LogAction m b -> LogAction m a
forall a b (m :: * -> *).
(a -> b) -> LogAction m b -> LogAction m a
cmap
{-# INLINE (>$<) #-}

-- | 'cmap' for convertions that may fail
cmapMaybe :: Applicative m => (a -> Maybe b) -> LogAction m b -> LogAction m a
cmapMaybe :: (a -> Maybe b) -> LogAction m b -> LogAction m a
cmapMaybe f :: a -> Maybe b
f (LogAction action :: b -> m ()
action) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (m () -> (b -> m ()) -> Maybe b -> m ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (() -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) b -> m ()
action (Maybe b -> m ()) -> (a -> Maybe b) -> a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Maybe b
f)
{-# INLINE cmapMaybe #-}

{- | Similar to `cmapMaybe` but for convertions that may fail inside a
monadic context.

@since 0.2.1.0
-}
cmapMaybeM :: Monad m => (a -> m (Maybe b)) -> LogAction m b -> LogAction m a
cmapMaybeM :: (a -> m (Maybe b)) -> LogAction m b -> LogAction m a
cmapMaybeM f :: a -> m (Maybe b)
f (LogAction action :: b -> m ()
action) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (m () -> (b -> m ()) -> Maybe b -> m ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (() -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) b -> m ()
action (Maybe b -> m ()) -> (a -> m (Maybe b)) -> a -> m ()
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< a -> m (Maybe b)
f)
{-# INLINE cmapMaybeM #-}

{- | This combinator is @>$@ from contravariant functor. Replaces all locations
in the output with the same value. The default definition is
@contramap . const@, so this is a more efficient version.

>>> "Hello?" &> ("OUT OF SERVICE" >$ logStringStdout)
OUT OF SERVICE
>>> ("OUT OF SERVICE" >$ logStringStdout) <& 42
OUT OF SERVICE
-}
infixl 4 >$
(>$) :: b -> LogAction m b -> LogAction m a
>$ :: b -> LogAction m b -> LogAction m a
(>$) b :: b
b (LogAction action :: b -> m ()
action) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (\_ -> b -> m ()
action b
b)

{- | 'cmapM' combinator is similar to 'cmap' but allows to call monadic
functions (functions that require extra context) to extend consumed value.
Consider the following example.

You have this logging record:

@
__data__ LogRecord = LR
    { lrTime    :: UTCTime
    , lrMessage :: Text
    }
@

and you also have logging consumer inside 'IO' for such record:

@
logRecordAction :: 'LogAction' IO LogRecord
@

But you need to return consumer only for 'Text' messages:

@
logTextAction :: 'LogAction' IO Text
@

If you have function that can extend 'Text' to @LogRecord@ like the function
below:

@
withTime :: 'Text' -> 'IO' LogRecord
withTime msg = __do__
    time <- getCurrentTime
    pure (LR time msg)
@

you can achieve desired behavior with 'cmapM' in the following way:

@
logTextAction :: 'LogAction' IO Text
logTextAction = 'cmapM' withTime myAction
@
-}
cmapM :: Monad m => (a -> m b) -> LogAction m b -> LogAction m a
cmapM :: (a -> m b) -> LogAction m b -> LogAction m a
cmapM f :: a -> m b
f (LogAction action :: b -> m ()
action) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (a -> m b
f (a -> m b) -> (b -> m ()) -> a -> m ()
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> b -> m ()
action)
{-# INLINE cmapM #-}

----------------------------------------------------------------------------
-- Divisible combinators
----------------------------------------------------------------------------

{- $divisible

Combinators that implement interface in the spirit of the following typeclass:

@
__class__ Contravariant f => Divisible f __where__
    conquer :: f a
    divide  :: (a -> (b, c)) -> f b -> f c -> f a
@
-}

{- | @divide@ combinator from @Divisible@ type class.

>>> logInt = LogAction print
>>> "ABC" &> divide (\s -> (s, length s)) logStringStdout logInt
ABC
3
-}
divide :: (Applicative m) => (a -> (b, c)) -> LogAction m b -> LogAction m c -> LogAction m a
divide :: (a -> (b, c)) -> LogAction m b -> LogAction m c -> LogAction m a
divide f :: a -> (b, c)
f (LogAction actionB :: b -> m ()
actionB) (LogAction actionC :: c -> m ()
actionC) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \(a -> (b, c)
f -> (b :: b
b, c :: c
c)) ->
    b -> m ()
actionB b
b m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> c -> m ()
actionC c
c
{-# INLINE divide #-}

{- | Monadic version of 'divide'.

@since 0.2.1.0
-}
divideM :: (Monad m) => (a -> m (b, c)) -> LogAction m b -> LogAction m c -> LogAction m a
divideM :: (a -> m (b, c)) -> LogAction m b -> LogAction m c -> LogAction m a
divideM f :: a -> m (b, c)
f (LogAction actionB :: b -> m ()
actionB) (LogAction actionC :: c -> m ()
actionC) =
    (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \(a -> m (b, c)
f -> m (b, c)
mbc) -> m (b, c)
mbc m (b, c) -> ((b, c) -> m ()) -> m ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\(b :: b
b, c :: c
c) -> b -> m ()
actionB b
b m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> c -> m ()
actionC c
c)
{-# INLINE divideM #-}

{- | @conquer@ combinator from @Divisible@ type class.

Concretely, this is a 'LogAction' that does nothing:

>>> conquer <& "hello?"
>>> "hello?" &> conquer
-}
conquer :: Applicative m => LogAction m a
conquer :: LogAction m a
conquer = LogAction m a
forall a. Monoid a => a
mempty
{-# INLINE conquer #-}

{- | Operator version of @'divide' 'id'@.

>>> logInt = LogAction print
>>> (logStringStdout >*< logInt) <& ("foo", 1)
foo
1
>>> (logInt >*< logStringStdout) <& (1, "foo")
1
foo
-}
infixr 4 >*<
(>*<) :: (Applicative m) => LogAction m a -> LogAction m b -> LogAction m (a, b)
(LogAction actionA :: a -> m ()
actionA) >*< :: LogAction m a -> LogAction m b -> LogAction m (a, b)
>*< (LogAction actionB :: b -> m ()
actionB) = ((a, b) -> m ()) -> LogAction m (a, b)
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (((a, b) -> m ()) -> LogAction m (a, b))
-> ((a, b) -> m ()) -> LogAction m (a, b)
forall a b. (a -> b) -> a -> b
$ \(a :: a
a, b :: b
b) ->
    a -> m ()
actionA a
a m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> b -> m ()
actionB b
b
{-# INLINE (>*<) #-}

{-| Perform a constant log action after another.

>>> logHello = LogAction (const (putStrLn "Hello!"))
>>> "Greetings!" &> (logStringStdout >* logHello)
Greetings!
Hello!
-}
infixr 4 >*
(>*) :: Applicative m => LogAction m a -> LogAction m () -> LogAction m a
(LogAction actionA :: a -> m ()
actionA) >* :: LogAction m a -> LogAction m () -> LogAction m a
>* (LogAction actionB :: () -> m ()
actionB) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \a :: a
a ->
    a -> m ()
actionA a
a m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> () -> m ()
actionB ()
{-# INLINE (>*) #-}

-- | A flipped version of '>*'
infixr 4 *<
(*<) :: Applicative m => LogAction m () -> LogAction m a -> LogAction m a
(LogAction actionA :: () -> m ()
actionA) *< :: LogAction m () -> LogAction m a -> LogAction m a
*< (LogAction actionB :: a -> m ()
actionB) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> LogAction m a) -> (a -> m ()) -> LogAction m a
forall a b. (a -> b) -> a -> b
$ \a :: a
a ->
    () -> m ()
actionA () m () -> m () -> m ()
forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> a -> m ()
actionB a
a
{-# INLINE (*<) #-}

----------------------------------------------------------------------------
-- Decidable combinators
----------------------------------------------------------------------------

{- $decidable

Combinators that implement interface in the spirit of the following typeclass:

@
__class__ Divisible f => Decidable f __where__
    lose   :: (a -> Void) -> f a
    choose :: (a -> Either b c) -> f b -> f c -> f a
@
-}

-- | @lose@ combinator from @Decidable@ type class.
lose :: (a -> Void) -> LogAction m a
lose :: (a -> Void) -> LogAction m a
lose f :: a -> Void
f = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (Void -> m ()
forall a. Void -> a
absurd (Void -> m ()) -> (a -> Void) -> a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Void
f)
{-# INLINE lose #-}

{- | @choose@ combinator from @Decidable@ type class.

>>> logInt = LogAction print
>>> f = choose (\a -> if a < 0 then Left "Negative" else Right a)
>>> f logStringStdout logInt <& 1
1
>>> f logStringStdout logInt <& (-1)
Negative
-}
choose :: (a -> Either b c) -> LogAction m b -> LogAction m c -> LogAction m a
choose :: (a -> Either b c)
-> LogAction m b -> LogAction m c -> LogAction m a
choose f :: a -> Either b c
f (LogAction actionB :: b -> m ()
actionB) (LogAction actionC :: c -> m ()
actionC) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((b -> m ()) -> (c -> m ()) -> Either b c -> m ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either b -> m ()
actionB c -> m ()
actionC (Either b c -> m ()) -> (a -> Either b c) -> a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Either b c
f)
{-# INLINE choose #-}

{- | Monadic version of 'choose'.

@since 0.2.1.0
-}
chooseM :: Monad m => (a -> m (Either b c)) -> LogAction m b -> LogAction m c -> LogAction m a
chooseM :: (a -> m (Either b c))
-> LogAction m b -> LogAction m c -> LogAction m a
chooseM f :: a -> m (Either b c)
f (LogAction actionB :: b -> m ()
actionB) (LogAction actionC :: c -> m ()
actionC) = (a -> m ()) -> LogAction m a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((b -> m ()) -> (c -> m ()) -> Either b c -> m ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either b -> m ()
actionB c -> m ()
actionC (Either b c -> m ()) -> (a -> m (Either b c)) -> a -> m ()
forall (m :: * -> *) b c a.
Monad m =>
(b -> m c) -> (a -> m b) -> a -> m c
<=< a -> m (Either b c)
f)
{-# INLINE chooseM #-}

{- | Operator version of @'choose' 'id'@.

>>> dontPrintInt = LogAction (const (putStrLn "Not printing Int"))
>>> Left 1 &> (dontPrintInt >|< logStringStdout)
Not printing Int
>>> (dontPrintInt >|< logStringStdout) <& Right ":)"
:)
-}
infixr 3 >|<
(>|<) :: LogAction m a -> LogAction m b -> LogAction m (Either a b)
(LogAction actionA :: a -> m ()
actionA) >|< :: LogAction m a -> LogAction m b -> LogAction m (Either a b)
>|< (LogAction actionB :: b -> m ()
actionB) = (Either a b -> m ()) -> LogAction m (Either a b)
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((a -> m ()) -> (b -> m ()) -> Either a b -> m ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either a -> m ()
actionA b -> m ()
actionB)
{-# INLINE (>|<) #-}

----------------------------------------------------------------------------
-- Comonadic combinators
----------------------------------------------------------------------------

{- $comonad

Combinators that implement interface in the spirit of the following typeclass:

@
__class__ Functor w => Comonad w __where__
    extract   :: w a -> a
    duplicate :: w a -> w (w a)
    extend    :: (w a -> b) -> w a -> w b
@
-}

{- | If @msg@ is 'Monoid' then 'extract' performs given log action by passing
'mempty' to it.

>>> logPrint :: LogAction IO [Int]; logPrint = LogAction print
>>> extract logPrint
[]
-}
extract :: Monoid msg => LogAction m msg -> m ()
extract :: LogAction m msg -> m ()
extract action :: LogAction m msg
action = LogAction m msg -> msg -> m ()
forall (m :: * -> *) msg. LogAction m msg -> msg -> m ()
unLogAction LogAction m msg
action msg
forall a. Monoid a => a
mempty
{-# INLINE extract #-}

-- TODO: write better motivation for comonads
{- | This is a /comonadic extend/. It allows you to chain different transformations on messages.

>>> f (LogAction l) = l ".f1" *> l ".f2"
>>> g (LogAction l) = l ".g"
>>> logStringStdout <& "foo"
foo
>>> extend f logStringStdout <& "foo"
foo.f1
foo.f2
>>> (extend g $ extend f logStringStdout) <& "foo"
foo.g.f1
foo.g.f2
>>> (logStringStdout =>> f =>> g) <& "foo"
foo.g.f1
foo.g.f2
-}
extend :: Semigroup msg => (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
extend :: (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
extend f :: LogAction m msg -> m ()
f (LogAction action :: msg -> m ()
action) = (msg -> m ()) -> LogAction m msg
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((msg -> m ()) -> LogAction m msg)
-> (msg -> m ()) -> LogAction m msg
forall a b. (a -> b) -> a -> b
$ \m :: msg
m -> LogAction m msg -> m ()
f (LogAction m msg -> m ()) -> LogAction m msg -> m ()
forall a b. (a -> b) -> a -> b
$ (msg -> m ()) -> LogAction m msg
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((msg -> m ()) -> LogAction m msg)
-> (msg -> m ()) -> LogAction m msg
forall a b. (a -> b) -> a -> b
$ \m' :: msg
m' -> msg -> m ()
action (msg
m msg -> msg -> msg
forall a. Semigroup a => a -> a -> a
<> msg
m')
{-# INLINE extend #-}

-- | 'extend' with the arguments swapped. Dual to '>>=' for a 'Monad'.
infixl 1 =>>
(=>>) :: Semigroup msg => LogAction m msg -> (LogAction m msg -> m ()) -> LogAction m msg
=>> :: LogAction m msg -> (LogAction m msg -> m ()) -> LogAction m msg
(=>>) = ((LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg)
-> LogAction m msg -> (LogAction m msg -> m ()) -> LogAction m msg
forall a b c. (a -> b -> c) -> b -> a -> c
flip (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
forall msg (m :: * -> *).
Semigroup msg =>
(LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
extend
{-# INLINE (=>>) #-}

-- | 'extend' in operator form.
infixr 1 <<=
(<<=) :: Semigroup msg => (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
<<= :: (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
(<<=) = (LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
forall msg (m :: * -> *).
Semigroup msg =>
(LogAction m msg -> m ()) -> LogAction m msg -> LogAction m msg
extend
{-# INLINE (<<=) #-}

{- | Converts any 'LogAction' that can log single message to the 'LogAction'
that can log two messages. The new 'LogAction' behaves in the following way:

1. Joins two messages of type @msg@ using '<>' operator from 'Semigroup'.
2. Passes resulted message to the given 'LogAction'.

>>> :{
let logger :: LogAction IO [Int]
    logger = logPrint
in duplicate logger <& ([3, 4], [42, 10])
:}
[3,4,42,10]

__Implementation note:__

True and fair translation of the @duplicate@ function from the 'Control.Comonad.Comonad'
interface should result in the 'LogAction' of the following form:

@
msg -> msg -> m ()
@

In order to capture this behavior, 'duplicate' should have the following type:

@
duplicate :: Semigroup msg => LogAction m msg -> LogAction (Compose ((->) msg) m) msg
@

However, it's quite awkward to work with such type. It's a known fact that the
following two types are isomorphic (see functions 'curry' and 'uncurry'):

@
a -> b -> c
(a, b) -> c
@

So using this fact we can come up with the simpler interface.
-}
duplicate :: forall msg m . Semigroup msg => LogAction m msg -> LogAction m (msg, msg)
duplicate :: LogAction m msg -> LogAction m (msg, msg)
duplicate (LogAction l :: msg -> m ()
l) = ((msg, msg) -> m ()) -> LogAction m (msg, msg)
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (((msg, msg) -> m ()) -> LogAction m (msg, msg))
-> ((msg, msg) -> m ()) -> LogAction m (msg, msg)
forall a b. (a -> b) -> a -> b
$ \(msg1 :: msg
msg1, msg2 :: msg
msg2) -> msg -> m ()
l (msg
msg1 msg -> msg -> msg
forall a. Semigroup a => a -> a -> a
<> msg
msg2)
{-# INLINE duplicate #-}


{- | Like 'duplicate' but why stop on a pair of two messages if you can log any
'Foldable' of messages?

>>> :{
let logger :: LogAction IO [Int]
    logger = logPrint
in multiplicate logger <& replicate 5 [1..3]
:}
[1,2,3,1,2,3,1,2,3,1,2,3,1,2,3]
-}
multiplicate
    :: forall f msg m .
       (Foldable f, Monoid msg)
    => LogAction m msg
    -> LogAction m (f msg)
multiplicate :: LogAction m msg -> LogAction m (f msg)
multiplicate (LogAction l :: msg -> m ()
l) = (f msg -> m ()) -> LogAction m (f msg)
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((f msg -> m ()) -> LogAction m (f msg))
-> (f msg -> m ()) -> LogAction m (f msg)
forall a b. (a -> b) -> a -> b
$ \msgs :: f msg
msgs -> msg -> m ()
l (f msg -> msg
forall (t :: * -> *) m. (Foldable t, Monoid m) => t m -> m
fold f msg
msgs)
{-# INLINE multiplicate #-}
{-# SPECIALIZE multiplicate :: Monoid msg => LogAction m msg -> LogAction m [msg] #-}
{-# SPECIALIZE multiplicate :: Monoid msg => LogAction m msg -> LogAction m (NonEmpty msg) #-}

{- | Like 'multiplicate' but instead of logging a batch of messages it logs each
of them separately.

>>> :{
let logger :: LogAction IO Int
    logger = logPrint
in separate logger <& [1..5]
:}
1
2
3
4
5

@since 0.2.1.0
-}
separate
    :: forall f msg m .
       (Traversable f, Applicative m)
    => LogAction m msg
    -> LogAction m (f msg)
separate :: LogAction m msg -> LogAction m (f msg)
separate (LogAction action :: msg -> m ()
action) = (f msg -> m ()) -> LogAction m (f msg)
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction ((msg -> m ()) -> f msg -> m ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ msg -> m ()
action)
{-# INLINE separate #-}
{-# SPECIALIZE separate :: Applicative m => LogAction m msg -> LogAction m [msg] #-}
{-# SPECIALIZE separate :: Applicative m => LogAction m msg -> LogAction m (NonEmpty msg) #-}
{-# SPECIALIZE separate :: LogAction IO msg -> LogAction IO [msg] #-}
{-# SPECIALIZE separate :: LogAction IO msg -> LogAction IO (NonEmpty msg) #-}

{- | Allows changing the internal monadic action.

Let's say we have a pure logger action using 'PureLogger'
and we want to log all messages into 'IO' instead.

If we provide the following function:

@
performPureLogsInIO :: PureLogger a -> IO a
@

then we can convert a logger action that uses a pure monad
to a one that performs the logging in the 'IO' monad using:

@
hoistLogAction performPureLogsInIO :: LogAction (PureLogger a) a -> LogAction IO a
@

@since 0.2.1.0
-}
hoistLogAction
    :: (forall x. m x -> n x)
    -> LogAction m a
    -> LogAction n a
hoistLogAction :: (forall x. m x -> n x) -> LogAction m a -> LogAction n a
hoistLogAction f :: forall x. m x -> n x
f (LogAction l :: a -> m ()
l) = (a -> n ()) -> LogAction n a
forall (m :: * -> *) msg. (msg -> m ()) -> LogAction m msg
LogAction (m () -> n ()
forall x. m x -> n x
f (m () -> n ()) -> (a -> m ()) -> a -> n ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> m ()
l)
{-# INLINE hoistLogAction #-}