{-# LANGUAGE Trustworthy #-}
-- |
-- Copyright: (c) 2021 Xy Ren
-- License: BSD3
-- Maintainer: xy.r@outlook.com
-- Stability: experimental
-- Portability: non-portable (GHC only)
module Cleff.Output
  ( -- * Effect
    Output (..)
    -- * Operations
  , output
    -- * Interpretations
  , outputToListState
  , outputToWriter
  , ignoreOutput
  , runOutputEff
  , mapOutput
  , bindOutput
  ) where

import           Cleff
import           Cleff.State
import           Cleff.Writer

-- * Effect

-- | An effect that is capable of producing outputs, for example writing to a log file or an output stream.
data Output o :: Effect where
  Output :: o -> Output o m ()

-- * Operations

makeEffect_ ''Output

-- | Produce an output value.
output :: Output o :> es => o -> Eff es ()

-- * Interpretations

-- | Run an 'Output' effect by accumulating a list. Note that outputs are being prepended to the head of the list, so
-- in many cases you would want to 'reverse' the result.
outputToListState :: Eff (Output o : es) ~> Eff (State [o] : es)
outputToListState :: Eff (Output o : es) a -> Eff (State [o] : es) a
outputToListState = Handler (Output o) (State [o] : es)
-> Eff (Output o : es) ~> Eff (State [o] : es)
forall (e' :: (Type -> Type) -> Type -> Type)
       (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e (e' : es) -> Eff (e : es) ~> Eff (e' : es)
reinterpret \case
  Output x -> ([o] -> [o]) -> Eff (State [o] : es) ()
forall s (es :: [(Type -> Type) -> Type -> Type]).
(State s :> es) =>
(s -> s) -> Eff es ()
modify (o
x o -> [o] -> [o]
forall a. a -> [a] -> [a]
:)

-- | Run an 'Output' effect by translating it into a 'Writer'.
outputToWriter :: (o -> o') -> Eff (Output o : es) ~> Eff (Writer o' : es)
outputToWriter :: (o -> o') -> Eff (Output o : es) ~> Eff (Writer o' : es)
outputToWriter o -> o'
f = Handler (Output o) (Writer o' : es)
-> Eff (Output o : es) ~> Eff (Writer o' : es)
forall (e' :: (Type -> Type) -> Type -> Type)
       (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e (e' : es) -> Eff (e : es) ~> Eff (e' : es)
reinterpret \case
  Output x -> o' -> Eff (Writer o' : es) ()
forall w (es :: [(Type -> Type) -> Type -> Type]).
(Writer w :> es) =>
w -> Eff es ()
tell (o' -> Eff (Writer o' : es) ()) -> o' -> Eff (Writer o' : es) ()
forall a b. (a -> b) -> a -> b
$ o -> o'
f o
x

-- | Ignore outputs of an 'Output' effect altogether.
ignoreOutput :: Eff (Output o : es) ~> Eff es
ignoreOutput :: Eff (Output o : es) a -> Eff es a
ignoreOutput = Handler (Output o) es -> Eff (Output o : es) ~> Eff es
forall (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e es -> Eff (e : es) ~> Eff es
interpret \case
  Output _ -> () -> Eff es ()
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure ()

-- | Run an 'Output' effect by performing a computation for each output.
runOutputEff :: (o -> Eff es ()) -> Eff (Output o : es) ~> Eff es
runOutputEff :: (o -> Eff es ()) -> Eff (Output o : es) ~> Eff es
runOutputEff o -> Eff es ()
m = Handler (Output o) es -> Eff (Output o : es) ~> Eff es
forall (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e es -> Eff (e : es) ~> Eff es
interpret \case
  Output x -> o -> Eff es ()
m o
x

-- | Transform an 'Output' effect into another one already in the effect stack, by a pure function.
--
-- @since 0.2.1.0
mapOutput :: Output o' :> es => (o -> o') -> Eff (Output o : es) ~> Eff es
mapOutput :: (o -> o') -> Eff (Output o : es) ~> Eff es
mapOutput o -> o'
f = Handler (Output o) es -> Eff (Output o : es) ~> Eff es
forall (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e es -> Eff (e : es) ~> Eff es
interpret \case
  Output x -> o' -> Eff es ()
forall o (es :: [(Type -> Type) -> Type -> Type]).
(Output o :> es) =>
o -> Eff es ()
output (o' -> Eff es ()) -> o' -> Eff es ()
forall a b. (a -> b) -> a -> b
$ o -> o'
f o
x

-- | Transform an 'Output' effect into another one already in the effect stack, by an effectful computation.
--
-- @since 0.2.1.0
bindOutput :: Output o' :> es => (o -> Eff es o') -> Eff (Output o : es) ~> Eff es
bindOutput :: (o -> Eff es o') -> Eff (Output o : es) ~> Eff es
bindOutput o -> Eff es o'
f = Handler (Output o) es -> Eff (Output o : es) ~> Eff es
forall (e :: (Type -> Type) -> Type -> Type)
       (es :: [(Type -> Type) -> Type -> Type]).
Handler e es -> Eff (e : es) ~> Eff es
interpret \case
  Output x -> o' -> Eff es ()
forall o (es :: [(Type -> Type) -> Type -> Type]).
(Output o :> es) =>
o -> Eff es ()
output (o' -> Eff es ()) -> Eff es o' -> Eff es ()
forall (m :: Type -> Type) a b. Monad m => (a -> m b) -> m a -> m b
=<< o -> Eff es o'
f o
x