effin-0.1.0.0: src/Control/Effect/Writer.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
#if MTL
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
#endif
module Control.Effect.Writer (
EffectWriter, Writer, runWriter,
tell, listen, listens, pass, censor
) where
import Control.Monad.Effect
import Control.Applicative ((<$>))
import Control.Arrow (second)
import Data.Monoid (Monoid (..))
#ifdef MTL
import qualified Control.Monad.Writer.Class as W
instance EffectWriter e es => W.MonadWriter e (Effect es) where
tell = tell
listen = listen
pass = pass
#endif
-- | An effect that allows accumulating output.
data Writer w a = Writer w a
deriving Functor
type EffectWriter w es = (Monoid w, Member (Writer w) es, w ~ WriterType es)
type family WriterType es where
WriterType (Writer w ': es) = w
WriterType (t ': es) = WriterType es
-- | Writes a value to the output.
tell :: EffectWriter w es => w -> Effect es ()
tell x = send (Writer x ())
-- | Executes a computation, and obtains the writer output.
-- The writer output of the inner computation is still
-- written to the writer output of the outer computation.
listen :: EffectWriter w es => Effect es a -> Effect es (a, w)
listen effect = do
value@(_, output) <- run effect
tell output
return value
where
run =
handle point
$ intercept bind
$ defaultRelay
-- | Like `listen`, but the writer output is run through a function.
listens :: EffectWriter w es => (w -> b) -> Effect es a -> Effect es (a, b)
listens f = fmap (second f) . listen
-- | Runs a computation that returns a value and a function,
-- applies the function to the writer output, and then returns the value.
pass :: EffectWriter w es => Effect es (a, w -> w) -> Effect es a
pass effect = do
((x, f), l) <- listen effect
tell (f l)
return x
-- | Applies a function to the writer output of a computation.
censor :: EffectWriter w es => (w -> w) -> Effect es a -> Effect es a
censor f effect = pass $ do
a <- effect
return (a, f)
-- | Completely handles a writer effect. The writer value must be a `Monoid`.
-- `mempty` is used as an initial value, and `mappend` is used to combine values.
-- Returns the result of the computation and the final output value.
runWriter :: Monoid w => Effect (Writer w ': es) a -> Effect es (a, w)
runWriter =
handle point
$ eliminate bind
$ defaultRelay
point :: Monoid w => a -> Effect es (a, w)
point x = return (x, mempty)
bind :: Monoid w => Writer w (Effect es (b, w)) -> Effect es (b, w)
bind (Writer l k) = second (mappend l) <$> k