-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLogger.hs
113 lines (90 loc) · 3.47 KB
/
Logger.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
{-# LANGUAGE TemplateHaskell #-}
-- | Example of a custom effect for logging.
module Example.Logger where
-- base
import Data.Coerce (coerce)
import Prelude hiding (print)
-- hspec
import Test.Hspec (Spec, it, shouldBe)
-- effet
import Control.Effect.Identity
import Control.Effect.Machinery
import Control.Effect.Reader
-- transformers
import qualified Control.Monad.Trans.State.Strict as S
import Hspec (print, shouldPrint)
--- Custom Effects -------------------------------------------------------------
-- | An effect for logging.
class Monad m => Logger m where
logStr :: String -> m ()
makeEffect ''Logger
--- Effect Interpretations -----------------------------------------------------
-- | An effect handler for 'Logger' which prints the logged strings.
newtype PrintLogger m a =
PrintLogger { unPrintLogger :: m a }
deriving (Applicative, Functor, Monad, MonadIO)
deriving (MonadTrans, MonadTransControl) via IdentityT
deriving (MonadBase b, MonadBaseControl b)
instance MonadIO m => Logger (PrintLogger m) where
logStr = print
runPrintLogger :: (Logger `Via` PrintLogger) m a -> m a
runPrintLogger = coerce
-- | An effect handler for 'Logger' which collects the logged strings in reverse
-- order (newest comes first).
newtype CollectLogger m a =
CollectLogger { unCollectLogger :: S.StateT [String] m a }
deriving (Applicative, Functor, Monad, MonadIO)
deriving (MonadTrans, MonadTransControl)
deriving (MonadBase b, MonadBaseControl b)
instance Monad m => Logger (CollectLogger m) where
logStr txt = CollectLogger $ S.modify (txt :)
runCollectLogger :: (Logger `Via` CollectLogger) m a -> m (a, [String])
runCollectLogger = flip S.runStateT [] . coerce
-- | An effect handler for 'Logger' which suppresses logging.
newtype NoLogger m a =
NoLogger { unNoLogger :: m a }
deriving (Applicative, Functor, Monad, MonadIO)
deriving (MonadTrans, MonadTransControl) via IdentityT
deriving (MonadBase b, MonadBaseControl b)
instance Monad m => Logger (NoLogger m) where
logStr _ = pure ()
runNoLogger :: (Logger `Via` NoLogger) m a -> m a
runNoLogger = coerce
--- Example Programs -----------------------------------------------------------
-- | Simple program which logs some steps.
logging :: (Logger m, MonadIO m) => m ()
logging = do
print "A"
logStr $ "Done A"
print "B"
logStr $ "Done B"
print "C"
logStr $ "Done C"
-- | Logs and returns the sum of a number and the reader value.
adding :: (Reader Int m, Logger m) => Int -> m Int
adding offset = do
num <- ask
logStr $ "Asked: " ++ show num
let result = offset + num
logStr $ "Sum: " ++ show result
pure result
--- Test Cases -----------------------------------------------------------------
spec :: Spec
spec = do
it "evaluates logging by suppression" $
( runNoLogger -- result: MonadIO m => m (), unified with IO ()
$ logging -- effects: Logger, MonadIO
) `shouldPrint`
"\"A\"\n\"B\"\n\"C\"\n"
it "evaluates logging by printing" $
( runPrintLogger -- result: MonadIO m => m (), unified with IO ()
$ logging -- effects: Logger, MonadIO
) `shouldPrint`
"\"A\"\n\"Done A\"\n\"B\"\n\"Done B\"\n\"C\"\n\"Done C\"\n"
it "evaluates logging by collecting" $
( runIdentity -- result: (Int, [String])
. runCollectLogger -- result: Monad m => m (Int, [String])
. runReader 1327 -- effects: Logger
$ adding 10 -- effects: Reader Int, Logger
) `shouldBe`
(1337, ["Sum: 1337", "Asked: 1327"])