{-# LANGUAGE BangPatterns, TemplateHaskell #-}

-- | Description: The 'Output' effect for sending side-effecting messages
module Polysemy.Output
  ( -- * Effect
    Output (..)

    -- * Actions
  , output

    -- * Interpretations
  , runOutputList
  , runLazyOutputList
  , runOutputMonoid
  , runLazyOutputMonoid
  , runOutputMonoidAssocR
  , runLazyOutputMonoidAssocR
  , runOutputMonoidIORef
  , runOutputMonoidTVar
  , outputToIOMonoid
  , outputToIOMonoidAssocR
  , ignoreOutput
  , runOutputBatched
  , runOutputSem
  ) where

import Data.IORef
import Control.Concurrent.STM
import qualified Control.Monad.Trans.Writer.Lazy as Lazy

import Data.Semigroup (Endo(..))
import Data.Bifunctor (first)
import Polysemy
import Polysemy.State
import Control.Monad (when)

import Polysemy.Internal.Union
import Polysemy.Internal.Writer


------------------------------------------------------------------------------
-- | An effect capable of sending messages. Useful for streaming output and for
-- logging.
data Output o m a where
  -- | Output a message.
  Output :: o -> Output o m ()

makeSem ''Output


------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into a list of its values.
--
-- @since 1.0.0.0
runOutputList
    :: forall o r a
     . Sem (Output o ': r) a
    -> Sem r ([o], a)
runOutputList :: forall o (r :: EffectRow) a. Sem (Output o : r) a -> Sem r ([o], a)
runOutputList = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first forall a. [a] -> [a]
reverse) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall s (r :: EffectRow) a.
s -> Sem (State s : r) a -> Sem r (s, a)
runState [] forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (e1 :: Effect) (e2 :: Effect) (r :: EffectRow) a.
FirstOrder e1 "reinterpret" =>
(forall (rInitial :: EffectRow) x.
 e1 (Sem rInitial) x -> Sem (e2 : r) x)
-> Sem (e1 : r) a -> Sem (e2 : r) a
reinterpret
  (\case
      Output o
o -> forall s (r :: EffectRow).
Member (State s) r =>
(s -> s) -> Sem r ()
modify' (o
o forall a. a -> [a] -> [a]
:)
  )
{-# INLINE runOutputList #-}


------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into a list of its values,
-- lazily.
--
-- __Warning: This inherits the nasty space leak issue of__
-- __'Lazy.WriterT'! Don't use this if you don't have to.__
--
-- @since 1.3.0.0
runLazyOutputList
    :: forall o r a
     . Sem (Output o ': r) a
    -> Sem r ([o], a)
runLazyOutputList :: forall o (r :: EffectRow) a. Sem (Output o : r) a -> Sem r ([o], a)
runLazyOutputList = forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runLazyOutputMonoidAssocR forall (f :: * -> *) a. Applicative f => a -> f a
pure
{-# INLINE runLazyOutputList #-}

------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into a monoid.
--
-- @since 1.0.0.0
runOutputMonoid
    :: forall o m r a
     . Monoid m
    => (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r (m, a)
runOutputMonoid :: forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runOutputMonoid o -> m
f = forall s (r :: EffectRow) a.
s -> Sem (State s : r) a -> Sem r (s, a)
runState forall a. Monoid a => a
mempty forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (e1 :: Effect) (e2 :: Effect) (r :: EffectRow) a.
FirstOrder e1 "reinterpret" =>
(forall (rInitial :: EffectRow) x.
 e1 (Sem rInitial) x -> Sem (e2 : r) x)
-> Sem (e1 : r) a -> Sem (e2 : r) a
reinterpret
  (\case
      Output o
o -> forall s (r :: EffectRow).
Member (State s) r =>
(s -> s) -> Sem r ()
modify' (forall a. Monoid a => a -> a -> a
`mappend` o -> m
f o
o)
  )
{-# INLINE runOutputMonoid #-}


------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into a monoid, and accumulate
-- it lazily.
--
-- __Warning: This inherits the nasty space leak issue of__
-- __'Lazy.WriterT'! Don't use this if you don't have to.__
--
-- @since 1.3.0.0
runLazyOutputMonoid
    :: forall o m r a
     . Monoid m
    => (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r (m, a)
runLazyOutputMonoid :: forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runLazyOutputMonoid o -> m
f = forall o (e :: Effect) (r :: EffectRow) a.
Monoid o =>
(forall (m :: * -> *) x.
 Monad m =>
 Weaving e (WriterT o m) x -> WriterT o m x)
-> Sem (e : r) a -> Sem r (o, a)
interpretViaLazyWriter forall a b. (a -> b) -> a -> b
$ \(Weaving Output o (Sem rInitial) a
e f ()
s forall x. f (Sem rInitial x) -> WriterT m m (f x)
_ f a -> x
ex forall x. f x -> Maybe x
_) ->
  case Output o (Sem rInitial) a
e of
    Output o
o -> f a -> x
ex f ()
s forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ forall (m :: * -> *) w. Monad m => w -> WriterT w m ()
Lazy.tell (o -> m
f o
o)

------------------------------------------------------------------------------
-- | Like 'runOutputMonoid', but right-associates uses of '<>'.
--
-- This asymptotically improves performance if the time complexity of '<>' for
-- the 'Monoid' depends only on the size of the first argument.
--
-- You should always use this instead of 'runOutputMonoid' if the monoid
-- is a list, such as 'String'.
--
-- @since 1.1.0.0
runOutputMonoidAssocR
    :: forall o m r a
     . Monoid m
    => (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r (m, a)
runOutputMonoidAssocR :: forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runOutputMonoidAssocR o -> m
f =
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (forall a. Endo a -> a -> a
`appEndo` forall a. Monoid a => a
mempty))
  forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runOutputMonoid (\o
o -> let !o' :: m
o' = o -> m
f o
o in forall a. (a -> a) -> Endo a
Endo (m
o' forall a. Semigroup a => a -> a -> a
<>))
{-# INLINE runOutputMonoidAssocR #-}

------------------------------------------------------------------------------
-- | Like 'runLazyOutputMonoid', but right-associates uses of '<>'.
--
-- This asymptotically improves performance if the time complexity of '<>' for
-- the 'Monoid' depends only on the size of the first argument.
--
-- You should always use this instead of 'runLazyOutputMonoid' if the monoid
-- is a list, such as 'String'.
--
-- __Warning: This inherits the nasty space leak issue of__
-- __'Lazy.WriterT'! Don't use this if you don't have to.__
--
-- @since 1.3.0.0
runLazyOutputMonoidAssocR
    :: forall o m r a
     . Monoid m
    => (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r (m, a)
runLazyOutputMonoidAssocR :: forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runLazyOutputMonoidAssocR o -> m
f =
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (forall a. Endo a -> a -> a
`appEndo` forall a. Monoid a => a
mempty))
  forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall o m (r :: EffectRow) a.
Monoid m =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
runLazyOutputMonoid (\o
o -> let o' :: m
o' = o -> m
f o
o in forall a. (a -> a) -> Endo a
Endo (m
o' forall a. Semigroup a => a -> a -> a
<>))
                              --   ^ N.B. No bang pattern
{-# INLINE runLazyOutputMonoidAssocR #-}

------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into atomic operations
-- over an 'IORef'.
--
-- @since 1.1.0.0
runOutputMonoidIORef
    :: forall o m r a
     . (Monoid m, Member (Embed IO) r)
    => IORef m
    -> (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r a
runOutputMonoidIORef :: forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
IORef m -> (o -> m) -> Sem (Output o : r) a -> Sem r a
runOutputMonoidIORef IORef m
ref o -> m
f = forall (e :: Effect) (r :: EffectRow) a.
FirstOrder e "interpret" =>
(forall (rInitial :: EffectRow) x. e (Sem rInitial) x -> Sem r x)
-> Sem (e : r) a -> Sem r a
interpret forall a b. (a -> b) -> a -> b
$ \case
  Output o
o -> forall (m :: * -> *) (r :: EffectRow) a.
Member (Embed m) r =>
m a -> Sem r a
embed forall a b. (a -> b) -> a -> b
$ forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef' IORef m
ref (\m
s -> let !o' :: m
o' = o -> m
f o
o in (m
s forall a. Semigroup a => a -> a -> a
<> m
o', ()))
{-# INLINE runOutputMonoidIORef #-}

------------------------------------------------------------------------------
-- | Run an 'Output' effect by transforming it into atomic operations
-- over a 'TVar'.
--
-- @since 1.1.0.0
runOutputMonoidTVar
    :: forall o m r a
     . (Monoid m, Member (Embed IO) r)
    => TVar m
    -> (o -> m)
    -> Sem (Output o ': r) a
    -> Sem r a
runOutputMonoidTVar :: forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
TVar m -> (o -> m) -> Sem (Output o : r) a -> Sem r a
runOutputMonoidTVar TVar m
tvar o -> m
f = forall (e :: Effect) (r :: EffectRow) a.
FirstOrder e "interpret" =>
(forall (rInitial :: EffectRow) x. e (Sem rInitial) x -> Sem r x)
-> Sem (e : r) a -> Sem r a
interpret forall a b. (a -> b) -> a -> b
$ \case
  Output o
o -> forall (m :: * -> *) (r :: EffectRow) a.
Member (Embed m) r =>
m a -> Sem r a
embed forall a b. (a -> b) -> a -> b
$ forall a. STM a -> IO a
atomically forall a b. (a -> b) -> a -> b
$ do
    m
s <- forall a. TVar a -> STM a
readTVar TVar m
tvar
    forall a. TVar a -> a -> STM ()
writeTVar TVar m
tvar forall a b. (a -> b) -> a -> b
$! m
s forall a. Semigroup a => a -> a -> a
<> o -> m
f o
o
{-# INLINE runOutputMonoidTVar #-}


--------------------------------------------------------------------
-- | Run an 'Output' effect in terms of atomic operations
-- in 'IO'.
--
-- Internally, this simply creates a new 'IORef', passes it to
-- 'runOutputMonoidIORef', and then returns the result and the final value
-- of the 'IORef'.
--
-- /Beware/: As this uses an 'IORef' internally,
-- all other effects will have local
-- state semantics in regards to 'Output' effects
-- interpreted this way.
-- For example, 'Polysemy.Error.throw' and 'Polysemy.Error.catch' will
-- never revert 'output's, even if 'Polysemy.Error.runError' is used
-- after 'outputToIOMonoid'.
--
-- @since 1.2.0.0
outputToIOMonoid
  :: forall o m r a
   . (Monoid m, Member (Embed IO) r)
  => (o -> m)
  -> Sem (Output o ': r) a
  -> Sem r (m, a)
outputToIOMonoid :: forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
outputToIOMonoid o -> m
f Sem (Output o : r) a
sem = do
  IORef m
ref <- forall (m :: * -> *) (r :: EffectRow) a.
Member (Embed m) r =>
m a -> Sem r a
embed forall a b. (a -> b) -> a -> b
$ forall a. a -> IO (IORef a)
newIORef forall a. Monoid a => a
mempty
  a
res <- forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
IORef m -> (o -> m) -> Sem (Output o : r) a -> Sem r a
runOutputMonoidIORef IORef m
ref o -> m
f Sem (Output o : r) a
sem
  m
end <- forall (m :: * -> *) (r :: EffectRow) a.
Member (Embed m) r =>
m a -> Sem r a
embed forall a b. (a -> b) -> a -> b
$ forall a. IORef a -> IO a
readIORef IORef m
ref
  forall (m :: * -> *) a. Monad m => a -> m a
return (m
end, a
res)

------------------------------------------------------------------------------
-- | Like 'outputToIOMonoid', but right-associates uses of '<>'.
--
-- This asymptotically improves performance if the time complexity of '<>' for
-- the 'Monoid' depends only on the size of the first argument.
--
-- You should always use this instead of 'outputToIOMonoid' if the monoid
-- is a list, such as 'String'.
--
-- /Beware/: As this uses an 'IORef' internally,
-- all other effects will have local
-- state semantics in regards to 'Output' effects
-- interpreted this way.
-- For example, 'Polysemy.Error.throw' and 'Polysemy.Error.catch' will
-- never revert 'output's, even if 'Polysemy.Error.runError' is used
-- after 'outputToIOMonoidAssocR'.
--
-- @since 1.2.0.0
outputToIOMonoidAssocR
  :: forall o m r a
   . (Monoid m, Member (Embed IO) r)
  => (o -> m)
  -> Sem (Output o ': r) a
  -> Sem r (m, a)
outputToIOMonoidAssocR :: forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
outputToIOMonoidAssocR o -> m
f =
    (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first) (forall a. Endo a -> a -> a
`appEndo` forall a. Monoid a => a
mempty)
  forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall o m (r :: EffectRow) a.
(Monoid m, Member (Embed IO) r) =>
(o -> m) -> Sem (Output o : r) a -> Sem r (m, a)
outputToIOMonoid (\o
o -> let !o' :: m
o' = o -> m
f o
o in forall a. (a -> a) -> Endo a
Endo (m
o' forall a. Semigroup a => a -> a -> a
<>))

------------------------------------------------------------------------------
-- | Run an 'Output' effect by ignoring it.
--
-- @since 1.0.0.0
ignoreOutput :: Sem (Output o ': r) a -> Sem r a
ignoreOutput :: forall o (r :: EffectRow) a. Sem (Output o : r) a -> Sem r a
ignoreOutput = forall (e :: Effect) (r :: EffectRow) a.
FirstOrder e "interpret" =>
(forall (rInitial :: EffectRow) x. e (Sem rInitial) x -> Sem r x)
-> Sem (e : r) a -> Sem r a
interpret forall a b. (a -> b) -> a -> b
$ \case
  Output o
_ -> forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
{-# INLINE ignoreOutput #-}


------------------------------------------------------------------------------
-- | Accumulate 'output's so they are delayed until they reach at least size
-- @size@.
--
-- If @size@ is 0, this interpretation will not emit anything in the resulting
-- 'Output' effect.
--
-- @since 1.0.0.0
runOutputBatched
    :: forall o r a
     . Member (Output [o]) r
    => Int
    -> Sem (Output o ': r) a
    -> Sem r a
runOutputBatched :: forall o (r :: EffectRow) a.
Member (Output [o]) r =>
Int -> Sem (Output o : r) a -> Sem r a
runOutputBatched Int
0 Sem (Output o : r) a
m = forall o (r :: EffectRow) a. Sem (Output o : r) a -> Sem r a
ignoreOutput Sem (Output o : r) a
m
runOutputBatched Int
size Sem (Output o : r) a
m = do
  ((Int
c, [o]
res), a
a) <-
    forall s (r :: EffectRow) a.
s -> Sem (State s : r) a -> Sem r (s, a)
runState (Int
0 :: Int, [] :: [o]) forall a b. (a -> b) -> a -> b
$ forall (e1 :: Effect) (e2 :: Effect) (r :: EffectRow) a.
FirstOrder e1 "reinterpret" =>
(forall (rInitial :: EffectRow) x.
 e1 (Sem rInitial) x -> Sem (e2 : r) x)
-> Sem (e1 : r) a -> Sem (e2 : r) a
reinterpret (\case
      Output o
o -> do
        (Int
count, [o]
acc) <- forall s (r :: EffectRow). Member (State s) r => Sem r s
get
        let newCount :: Int
newCount = Int
1 forall a. Num a => a -> a -> a
+ Int
count
            newAcc :: [o]
newAcc = o
o forall a. a -> [a] -> [a]
: [o]
acc
        if Int
newCount forall a. Ord a => a -> a -> Bool
< Int
size
          then forall s (r :: EffectRow). Member (State s) r => s -> Sem r ()
put (Int
newCount, [o]
newAcc)
          else do
            forall o (r :: EffectRow). Member (Output o) r => o -> Sem r ()
output (forall a. [a] -> [a]
reverse [o]
newAcc)
            forall s (r :: EffectRow). Member (State s) r => s -> Sem r ()
put (Int
0 :: Int, [] :: [o])
    ) Sem (Output o : r) a
m
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
c forall a. Ord a => a -> a -> Bool
> Int
0) forall a b. (a -> b) -> a -> b
$ forall o (r :: EffectRow). Member (Output o) r => o -> Sem r ()
output @[o] (forall a. [a] -> [a]
reverse [o]
res)
  forall (f :: * -> *) a. Applicative f => a -> f a
pure a
a

------------------------------------------------------------------------------
-- | Runs an 'Output' effect by running a monadic action for each of its
-- values.
runOutputSem :: (o -> Sem r ()) -> Sem (Output o ': r) a -> Sem r a
runOutputSem :: forall o (r :: EffectRow) a.
(o -> Sem r ()) -> Sem (Output o : r) a -> Sem r a
runOutputSem o -> Sem r ()
act = forall (e :: Effect) (r :: EffectRow) a.
FirstOrder e "interpret" =>
(forall (rInitial :: EffectRow) x. e (Sem rInitial) x -> Sem r x)
-> Sem (e : r) a -> Sem r a
interpret forall a b. (a -> b) -> a -> b
$ \case
    Output o
o -> o -> Sem r ()
act o
o
{-# INLINE runOutputSem #-}