content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE DerivingVia #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE TypeApplications #-}
  {-# LANGUAGE TypeInType #-}
  
  -- | Example uses and instances of the @HasWriter@ capability.
  module Writer where
  
  import Capability.State
  import Capability.Sink
  import Capability.Source
  import Capability.Writer
  import Control.Monad.State.Strict (State, StateT (..), runState)
  import Data.Monoid (Sum (..))
  import Test.Hspec
  
  ----------------------------------------------------------------------
  -- Example Programs
  
  -- | Increase a counter using a writer monad.
  useWriter :: HasWriter "count-writer" (Sum Int) m => m ()
  useWriter = censor @"count-writer" (*2) {- double the eventual result -} $ do
    -- Add 3 and retrieve result
    ((), count) <- listen @"count-writer" (tell @"count-writer" 3)
    -- Duplicate
    tell @"count-writer" count
  
  
  -- | Mix writer and state monad operations on the same tag.
  --
  -- Note, mixing capabilities on the same tag like this is inadvisable in
  -- real applications.  The @HasState@ capability could be used to clear the
  -- accumulated outcome of the @HasWriter@ capability.
  mixWriterState
    :: (HasState "count-state" Int m, HasWriter "count-writer" (Sum Int) m)
    => m Int
  mixWriterState = do
    tell @"count-writer" 1
    one <- get @"count-state"
    tell @"count-writer" $ Sum one
    pure one
  
  
  ----------------------------------------------------------------------
  -- Instances
  
  -- | @HasWriter w@ capability derived from @HasState w@.
  --
  -- The monoid instance ('Data.Monoid.Sum') is also selected in the deriving
  -- via clause.
  newtype WriterM a = WriterM (State Int a)
    deriving (Functor, Applicative, Monad)
    deriving (HasSink "count-writer" (Sum Int), HasWriter "count-writer" (Sum Int))
      via WriterLog (Coerce (Sum Int) (MonadState (State Int)))
  
  runWriterM :: WriterM a -> (a, Int)
  runWriterM (WriterM m) = runState m 0
  
  
  -- | @HasWriter w@ capability derived from @HasState w@, while also exposing
  -- the underlying @HasState@.
  --
  -- Note, that this is inadvisable in real applications.
  -- See caveat on 'mixWriterState'.
  newtype BadWriterM a = BadWriterM (State Int a)
    deriving (Functor, Applicative, Monad)
    deriving (HasSink "count-writer" (Sum Int), HasWriter "count-writer" (Sum Int))
      via WriterLog (Coerce (Sum Int) (MonadState (State Int)))
    deriving (HasSource "count-state" Int, HasSink "count-state" Int, HasState "count-state" Int)
      via MonadState (State Int)
  
  runBadWriterM :: BadWriterM a -> (a, Int)
  runBadWriterM (BadWriterM m) = runState m 0
  
  
  ----------------------------------------------------------------------
  -- Test Cases
  
  spec :: Spec
  spec = do
    describe "WriterM" $
      it "evaluates useWriter" $
        runWriterM useWriter `shouldBe` ((), 12)
    describe "BadWriterM" $ do
      it "evaluates useWriter" $
        runBadWriterM useWriter `shouldBe` ((), 12)
      it "evaluates mixWriterState" $
        runBadWriterM mixWriterState `shouldBe` (1, 2)
  