{-# 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.Stream 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" (Sum Int) m => m () useWriter = do -- Add 3 and retrieve result ((), count) <- listen @"count" (tell @"count" 3) -- Duplicate tell @"count" 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" Int m, HasWriter "count" (Sum Int) m) => m Int mixWriterState = do tell @"count" 1 one <- get @"count" tell @"count" $ 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 (HasStream "count" (Sum Int), HasWriter "count" (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 (HasStream "count" (Sum Int), HasWriter "count" (Sum Int)) via WriterLog (Coerce (Sum Int) (MonadState (State Int))) deriving (HasState "count" 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` ((), 6) describe "BadWriterM" $ do it "evaluates useWriter" $ runBadWriterM useWriter `shouldBe` ((), 6) it "evaluates mixWriterState" $ runBadWriterM mixWriterState `shouldBe` (1, 2)