我只是想设计一个基于以下两个函数的简单日志记录系统:
-- Prints before (if logging is on), does action,
-- prints after (if logging is on), returns action
log :: b -> c -> IO a -> IO a
log before after action = ...
-- Sets logging on/off for the duration of the passed action.
setLog :: Bool -> IO a -> IO a
setLog doLog action = ...
即
setLog False $
log "This doesn't get printed" "Neither does this" (putStr "But this does")
只会打印:
"But this does"
但是这个:
setLog True $
log "This does get printed" "This does too" (putStr "And this does")
会打印:
"This does get printed"
"And this does"
"This does too"
这是我正在尝试设置的简化版本。 或者,如果有更好的方法来解决这个问题,我很高兴听到它。
答案 0 :(得分:1)
您可以使用StateT
monad变换器轻松完成此操作。举个简短的例子:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Applicative
import Control.Monad
import Control.Monad.State
data LogState = LogOff | LogOn
deriving (Eq, Ord, Enum, Bounded, Show, Read)
newtype LogT m a = LogT { unLogT :: StateT LogState m a}
deriving (Functor, Monad, Applicative, MonadState LogState, MonadTrans, MonadIO)
runLog :: Monad m => LogT m a -> m a
runLog action = evalStateT (unLogT action) LogOn
loggingEnabled :: (Functor m, Monad m) => LogT m Bool
loggingEnabled = fmap (== LogOn) get
log' :: (Functor m, Monad m) => (l -> LogT m ()) -> l -> l -> m a -> LogT m a
log' logger before after action = do
doLog <- loggingEnabled
when doLog $ logger before
result <- lift action
when doLog $ logger after
return result
-- The function `log` already exists in Prelude as the logarithm, use a different name
log_ :: (Functor m, MonadIO m) => String -> String -> m a -> LogT m a
log_ = log' (liftIO . putStrLn)
enableLogging :: Monad m => LogT m ()
enableLogging = put LogOn
disableLogging :: Monad m => LogT m ()
disableLogging = put LogOff
main :: IO ()
main = runLog $ do
enableLogging
log_ "You'll see this" "Then this" $
putStrLn "First action"
disableLogging
log_ "But not this" "or this" $
putStrLn "Second action"
感谢GeneralizedNewtypeDeriving
,我们可以自动为新的monad转换器生成有用的实例,例如Functor
,Monad
,MonadIO
和MonadTrans
。我们可以像使用StateT LogState
monad一样使用它,并使用它来存储是否启用了日志记录。如果您愿意,您可以简单地扩展这个,例如,如果您想控制日志记录级别(DEBUG / INFO / WARNING / ERROR),只需更改LogState
以满足您的需求。我还将log'
留在那里作为通用记录器,以防您希望将其写入WriterT
monad,或者写入文件或完全执行其他操作。它非常灵活。
答案 1 :(得分:0)
setLog :: Bool -> ((String -> IO ()) -> IO a) -> IO a
setLog True = ($ putStrLn)
setLog False = ($ (const $ return ()))
log' f x y ac = f x >> ac >>= \a -> f y >> return a
test0 = setLog False $ \f -> do
log' f "This doesn't get printed" "Neither does this" (putStr "But this does")
test1 = setLog True $ \f -> do
log' f "This does get printed" "This does too" (putStr "And this does")