effet-0.1.0.0: src/Control/Effect/RWS.hs
{-# LANGUAGE TemplateHaskell #-}
-----------------------------------------------------------------------------
-- |
-- Module : Control.Effect.RWS
-- Copyright : (c) Michael Szvetits, 2020
-- License : BSD3 (see the file LICENSE)
-- Maintainer : typedbyte@qualified.name
-- Stability : stable
-- Portability : portable
--
-- The effect that combines the reader, writer and state effect, similar to the
-- @MonadRWS@ type class from the @mtl@ library.
--
-- Lazy and strict interpretations of the effect are available here:
-- "Control.Effect.RWS.Lazy" and "Control.Effect.RWS.Strict".
-----------------------------------------------------------------------------
module Control.Effect.RWS
( -- * Tagged RWS Effect
RWS'(..)
-- * Untagged RWS Effect
-- | If you don't require disambiguation of multiple RWS effects
-- (i.e., you only have one RWS effect in your monadic context),
-- it is recommended to always use the untagged RWS effect.
, RWS
, ask
, local
, tell
, listen
, censor
, get
, put
-- * Convenience Functions
-- ** Reader Convenience
-- | If you don't require disambiguation of multiple RWS effects
-- (i.e., you only have one RWS effect in your monadic context),
-- it is recommended to always use the untagged functions.
, asks'
, asks
-- ** Writer Convenience
-- | If you don't require disambiguation of multiple RWS effects
-- (i.e., you only have one RWS effect in your monadic context),
-- it is recommended to always use the untagged functions.
, listens'
, listens
-- ** State Convenience
-- | If you don't require disambiguation of multiple RWS effects
-- (i.e., you only have one RWS effect in your monadic context),
-- it is recommended to always use the untagged functions.
, gets'
, gets
, modify'
, modify
, modifyStrict'
, modifyStrict
-- * Interpretations
, Separation(..)
, runSeparatedRWS'
, runSeparatedRWS
-- * Tagging and Untagging
-- | Conversion functions between the tagged and untagged RWS effect,
-- usually used in combination with type applications, like:
--
-- @
-- 'tagRWS'' \@\"newTag\" program
-- 'retagRWS'' \@\"oldTag\" \@\"newTag\" program
-- 'untagRWS'' \@\"erasedTag\" program
-- @
--
, tagRWS'
, retagRWS'
, untagRWS'
) where
-- base
import Data.Coerce (coerce)
import Data.Tuple (swap)
-- transformers
import qualified Control.Monad.Trans.RWS.Lazy as Lazy
import qualified Control.Monad.Trans.RWS.CPS as Strict
import qualified Control.Effect.Reader as R
import qualified Control.Effect.State as S
import qualified Control.Effect.Writer as W
import Control.Effect.Machinery
-- | An effect that adds the following features to a given computation:
--
-- * (R) an immutable environment (the \"reader\" part)
-- * (W) a write-only, accumulated output (the \"writer\" part)
-- * (S) a mutable state (the \"state\" part)
class Monad m => RWS' tag r w s m | tag m -> r w s where
-- | Gets the environment.
ask' :: m r
-- | Executes a sub-computation in a modified environment.
local' :: (r -> r) -- ^ The function to modify the environment.
-> m a -- ^ The sub-computation to run in the modified environment.
-> m a -- ^ The result of the sub-computation.
-- | Produces the output @w@. In other words, @w@ is appended to the accumulated output.
tell' :: w -> m ()
-- | Executes a sub-computation and appends @w@ to the accumulated output.
listen' :: m a -> m (w, a)
-- | Executes a sub-computation and applies the function to its output.
censor' :: (w -> w) -- ^ The function which is applied to the output.
-> m a -- ^ The sub-computation which produces the modified output.
-> m a -- ^ The result of the sub-computation.
-- | Gets the current state.
get' :: m s
-- | Replaces the state with a new value.
put' :: s -> m ()
makeTaggedEffect ''RWS'
instance (Monad m, Monoid w) => RWS' tag r w s (Lazy.RWST r w s m) where
ask' = Lazy.ask
{-# INLINE ask' #-}
local' = Lazy.local
{-# INLINE local' #-}
tell' = Lazy.tell
{-# INLINE tell' #-}
listen' = fmap swap . Lazy.listen
{-# INLINE listen' #-}
censor' = Lazy.censor
{-# INLINE censor' #-}
get' = Lazy.get
{-# INLINE get' #-}
put' = Lazy.put
{-# INLINE put' #-}
instance (Monad m, Monoid w) => RWS' tag r w s (Strict.RWST r w s m) where
ask' = Strict.ask
{-# INLINE ask' #-}
local' = Strict.local
{-# INLINE local' #-}
tell' = Strict.tell
{-# INLINE tell' #-}
listen' = fmap swap . Strict.listen
{-# INLINE listen' #-}
censor' = Strict.censor
{-# INLINE censor' #-}
get' = Strict.get
{-# INLINE get' #-}
put' = Strict.put
{-# INLINE put' #-}
-- | Gets a specific component of the environment, using the provided projection function.
asks' :: forall tag r w s m a. RWS' tag r w s m
=> (r -> a) -- ^ The projection function to apply to the environment.
-> m a -- ^ The result of the projection.
asks' = flip fmap (ask' @tag)
{-# INLINE asks' #-}
-- | The untagged version of 'asks''.
asks :: RWS r w s m => (r -> a) -> m a
asks = asks' @G
{-# INLINE asks #-}
-- | Executes a sub-computation and applies the function to its output, thus adding
-- an additional value to the result of the sub-computation.
listens' :: forall tag r w s b m a. RWS' tag r w s m
=> (w -> b) -- ^ The function which is applied to the output.
-> m a -- ^ The sub-computation which produces the modified output.
-> m (b, a) -- ^ The result of the sub-computation, including the modified output.
listens' f action = do
~(w, a) <- listen' @tag action
pure (f w, a)
{-# INLINE listens' #-}
-- | The untagged version of 'listens''.
listens :: RWS r w s m => (w -> b) -> m a -> m (b, a)
listens = listens' @G
{-# INLINE listens #-}
-- | Gets a specific component of the state, using the provided projection function.
gets' :: forall tag r w s m a. RWS' tag r w s m => (s -> a) -> m a
gets' f = fmap f (get' @tag)
{-# INLINE gets' #-}
-- | The untagged version of 'gets''.
gets :: RWS r w s m => (s -> a) -> m a
gets f = fmap f get
{-# INLINE gets #-}
-- | Modifies the state, using the provided function.
modify' :: forall tag r w s m. RWS' tag r w s m => (s -> s) -> m ()
modify' f = do
s <- get' @tag
put' @tag (f s)
{-# INLINE modify' #-}
-- | The untagged version of 'modify''.
modify :: RWS r w s m => (s -> s) -> m ()
modify = modify' @G
{-# INLINE modify #-}
-- | Modifies the state, using the provided function.
-- The computation is strict in the new state.
modifyStrict' :: forall tag r w s m. RWS' tag r w s m => (s -> s) -> m ()
modifyStrict' f = do
s <- get' @tag
put' @tag $! f s
{-# INLINE modifyStrict' #-}
-- | The untagged version of 'modifyStrict''.
modifyStrict :: RWS r w s m => (s -> s) -> m ()
modifyStrict = modifyStrict' @G
{-# INLINE modifyStrict #-}
-- | The separation interpreter of the RWS effect. This type implements the 'RWS''
-- type class by splitting the effect into separate 'R.Reader'', 'W.Writer'' and
-- 'S.State'' effects which can then be interpreted individually.
--
-- When interpreting the effect, you usually don\'t interact with this type directly,
-- but instead use one of its corresponding interpretation functions.
newtype Separation m a =
Separation { runSeparation :: m a }
deriving (Applicative, Functor, Monad, MonadIO)
deriving (MonadTrans, MonadTransControl) via Default
deriving (MonadBase b, MonadBaseControl b)
instance (R.Reader' tag r m, W.Writer' tag w m, S.State' tag s m) => RWS' tag r w s (Separation m) where
ask' = Separation (R.ask' @tag)
{-# INLINE ask' #-}
local' f = Separation . R.local' @tag f . runSeparation
{-# INLINE local' #-}
tell' = Separation . W.tell' @tag
{-# INLINE tell' #-}
listen' = Separation . W.listen' @tag . runSeparation
{-# INLINE listen' #-}
censor' f = Separation . W.censor' @tag f . runSeparation
{-# INLINE censor' #-}
get' = Separation (S.get' @tag)
{-# INLINE get' #-}
put' = Separation . S.put' @tag
{-# INLINE put' #-}
-- | Runs the RWS effect via separation.
runSeparatedRWS' :: (RWS' tag r w s `Via` Separation) m a -- ^ The program whose RWS effect should be handled.
-> m a -- ^ The program with its RWS effect handled.
runSeparatedRWS' = coerce
{-# INLINE runSeparatedRWS' #-}
-- | The untagged version of 'runSeparatedRWS''.
runSeparatedRWS :: (RWS r w s `Via` Separation) m a -> m a
runSeparatedRWS = coerce
{-# INLINE runSeparatedRWS #-}