在 Writer monad 中传递和收听有什么意义?
What is the point of pass and listen in Writer monad?
我一直在 learnyouahaskell 书中学习有关 monad 的知识。
在阅读了 writer monad 之后,我决定查看 Control.Monad.Writer.Class.
的文档
在那里我看到他们还实现了 listen
和 pass
功能,但我无法理解它们的用途。谁能给我一个很好的例子,这样我就可以理解如何使用 listen
和 pass
?
这是来自 Control.Monad.Trans.Writer.Strict
的代码,它定义了 listen
和 pass
:
listen :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
listen m = WriterT $ do
(a, w) <- runWriterT m
return ((a, w), w)
pass :: (Monoid w, Monad m) => WriterT w m (a, w -> w) -> WriterT w m a
pass m = WriterT $ do
((a, f), w) <- runWriterT m
return (a, f w)
WriterT
是一个 monad 转换器,它为其他 monad 提供 Writer
功能,简单的 Writer
类型定义为 type Writer w = WriterT w Identity
,在 Identity
monad绑定函数(<-
和 >>=
)只是变量绑定,所以如果我们分解上面代码的单子部分:
listen :: (Monoid w) => Writer w a -> Writer w (a, w)
listen m = Writer $
let (a, w) = runWriter m
in ((a, w), w)
pass :: (Monoid w) => Writer w (a, w -> w) -> Writer w a
pass m = Writer $
let ((a, f), w) = runWriter m
in (a, f w)
现在很清楚,listen
使您可以访问 Writer monad 中的 Writer 操作生成的日志,pass
为您提供了一种在 Writer monad 中更改日志的方法。
假设您有一个 Writer [String]
并希望仅当它生成的日志满足某些条件时才记录操作:
deleteOn :: (Monoid w) => (w -> Bool) -> Writer w a -> Writer w a
deleteOn p m = pass $ do
(a, w) <- listen m
if p w
then return (a, id)
else return (a, const mempty)
-- Or pass alone
deleteOn' :: (Monoid w) => (w -> Bool) -> Writer w a -> Writer w a
deleteOn' p m = pass $ do
a <- m
return (a, (\w -> if p w then mempty else w))
logTwo :: Writer [String] ()
logTwo = do
deleteOn ((> 5) . length . head) $ tell ["foo"]
deleteOn ((> 5) . length . head) $ tell ["foobar"]
{-
*Main> runWriter logTwo
((),["foo"])
-}
在 Writer
中,您无法检查写入的内容,直到您使用 运行(或 "unwrap")monad,使用 execWriter
或 runWriter
.但是,您可以使用 listen
检查某些 sub-action 写入写入器的内容,然后再将值附加到写入器状态,并且您可以使用 pass
修改写入的内容。
考虑这个使用 WriterT
进行日志记录的应用程序示例:
import Control.Monad.Writer
-- Monad stack for the application uses IO, wrapped in a logging WriterT
type App a = WriterT [String] IO a
-- utility to write to the log
logMsg :: String -> App ()
logMsg msg = tell [msg]
-- gets an Int from user input (and logs what it does)
getInt :: App Int
getInt = do
logMsg "getting data"
n <- liftIO getLine
logMsg $ "got line: " ++ show n
return . read $ n
-- application logic that uses getInt and increments the result by 1
app :: App Int
app = do
n <- getInt
return $ n + 1
-- main code runs the application and prints the log
main = do
(res, logs) <- runWriterT app
print $ "Result = " ++ show res
putStrLn "Log: "
mapM_ putStrLn logs
现在,出于某种原因,在 app
中,我想知道 getInt
向日志写入了哪些消息。我可以用 listen
:
app :: App Int
app = do
(n, logs) <- listen getInt
let numLogLines = length logs
logMsg $ "getInt logged " ++ show numLogLines ++ " lines"
return $ n + 1
我一直在 learnyouahaskell 书中学习有关 monad 的知识。 在阅读了 writer monad 之后,我决定查看 Control.Monad.Writer.Class.
的文档在那里我看到他们还实现了 listen
和 pass
功能,但我无法理解它们的用途。谁能给我一个很好的例子,这样我就可以理解如何使用 listen
和 pass
?
这是来自 Control.Monad.Trans.Writer.Strict
的代码,它定义了 listen
和 pass
:
listen :: (Monoid w, Monad m) => WriterT w m a -> WriterT w m (a, w)
listen m = WriterT $ do
(a, w) <- runWriterT m
return ((a, w), w)
pass :: (Monoid w, Monad m) => WriterT w m (a, w -> w) -> WriterT w m a
pass m = WriterT $ do
((a, f), w) <- runWriterT m
return (a, f w)
WriterT
是一个 monad 转换器,它为其他 monad 提供 Writer
功能,简单的 Writer
类型定义为 type Writer w = WriterT w Identity
,在 Identity
monad绑定函数(<-
和 >>=
)只是变量绑定,所以如果我们分解上面代码的单子部分:
listen :: (Monoid w) => Writer w a -> Writer w (a, w)
listen m = Writer $
let (a, w) = runWriter m
in ((a, w), w)
pass :: (Monoid w) => Writer w (a, w -> w) -> Writer w a
pass m = Writer $
let ((a, f), w) = runWriter m
in (a, f w)
现在很清楚,listen
使您可以访问 Writer monad 中的 Writer 操作生成的日志,pass
为您提供了一种在 Writer monad 中更改日志的方法。
假设您有一个 Writer [String]
并希望仅当它生成的日志满足某些条件时才记录操作:
deleteOn :: (Monoid w) => (w -> Bool) -> Writer w a -> Writer w a
deleteOn p m = pass $ do
(a, w) <- listen m
if p w
then return (a, id)
else return (a, const mempty)
-- Or pass alone
deleteOn' :: (Monoid w) => (w -> Bool) -> Writer w a -> Writer w a
deleteOn' p m = pass $ do
a <- m
return (a, (\w -> if p w then mempty else w))
logTwo :: Writer [String] ()
logTwo = do
deleteOn ((> 5) . length . head) $ tell ["foo"]
deleteOn ((> 5) . length . head) $ tell ["foobar"]
{-
*Main> runWriter logTwo
((),["foo"])
-}
在 Writer
中,您无法检查写入的内容,直到您使用 运行(或 "unwrap")monad,使用 execWriter
或 runWriter
.但是,您可以使用 listen
检查某些 sub-action 写入写入器的内容,然后再将值附加到写入器状态,并且您可以使用 pass
修改写入的内容。
考虑这个使用 WriterT
进行日志记录的应用程序示例:
import Control.Monad.Writer
-- Monad stack for the application uses IO, wrapped in a logging WriterT
type App a = WriterT [String] IO a
-- utility to write to the log
logMsg :: String -> App ()
logMsg msg = tell [msg]
-- gets an Int from user input (and logs what it does)
getInt :: App Int
getInt = do
logMsg "getting data"
n <- liftIO getLine
logMsg $ "got line: " ++ show n
return . read $ n
-- application logic that uses getInt and increments the result by 1
app :: App Int
app = do
n <- getInt
return $ n + 1
-- main code runs the application and prints the log
main = do
(res, logs) <- runWriterT app
print $ "Result = " ++ show res
putStrLn "Log: "
mapM_ putStrLn logs
现在,出于某种原因,在 app
中,我想知道 getInt
向日志写入了哪些消息。我可以用 listen
:
app :: App Int
app = do
(n, logs) <- listen getInt
let numLogLines = length logs
logMsg $ "getInt logged " ++ show numLogLines ++ " lines"
return $ n + 1