Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of pass and listen in Writer monad? [duplicate]

I have been learning about monads in the book learnyouahaskell. After reading about the writer monad, I decided to check the documentation of the Control.Monad.Writer.Class.

There I saw that they also implemented the listen and pass functions but I was not able to understand what they are used for. Could someone give me a good example so I can understand how to use listen and pass?

like image 713
demathieu Avatar asked Jan 16 '16 20:01

demathieu


People also ask

How does a monad work?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

Is maybe a monad?

The Maybe sum type is a useful data type that forms a functor. Like many other useful functors, it also forms a monad.

How do you make a monad in Haskell?

To create a monad, it is not enough just to declare a Haskell instance of the Monad class with the correct type signatures. To be a proper monad, the return and >>= functions must work together according to three laws: (return x) >>= f ==== f x.

Is list a monad Haskell?

Lists are a fundamental part of Haskell, and we've used them extensively before getting to this chapter. The novel insight is that the list type is a monad too! As monads, lists are used to model nondeterministic computations which may return an arbitrary number of results.


2 Answers

Here is code from Control.Monad.Trans.Writer.Strict that defined listen and 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 is a monad transformer which provides Writer functionality to other monads, the simple Writer type is defined as type Writer w = WriterT w Identity, in Identity monad the bind function (<- and >>=) is just variable binding, so if we break down the monadic part of the code above:

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)

It's now clear that listen gives you access to the log produced by a Writer action inside the Writer monad, and pass gives you a way to alter the log inside the Writer monad.

Assume you have a Writer [String] and want to log an action only if the log produced by it satisfies some condition:

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"])
-}
like image 92
zakyggaps Avatar answered Nov 15 '22 10:11

zakyggaps


Inside a Writer you can't inspect what has been written, until you run (or "unwrap") the monad, using execWriter or runWriter. However, you can use listen to inspect what some sub-action wrote to the writer, before the value is appended to the writer state, and you can use pass to modify what is written.

Consider this example of an application that uses WriterT for logging:

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

Now, for some reason, inside app, I want to know what messages getInt wrote to the log. I can do that with listen:

app :: App Int
app = do
    (n, logs) <- listen getInt
    let numLogLines = length logs
    logMsg $ "getInt logged " ++ show numLogLines ++ " lines"
    return $ n + 1
like image 20
Peter Hall Avatar answered Nov 15 '22 09:11

Peter Hall