Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to play with Control.Monad.Writer in haskell?

Tags:

haskell

monads

I'm new to functional programming and recently learning at Learn You a Haskell, but when I went through this chapter, I got stuck with the program below:

import Control.Monad.Writer    logNumber :: Int -> Writer [String] Int   logNumber x = Writer (x, ["Got number: " ++ show x])    multWithLog :: Writer [String] Int   multWithLog = do       a <- logNumber 3       b <- logNumber 5       return (a*b) 

I saved these lines in a .hs file and but failed to import it into my ghci which complained:

more1.hs:4:15:     Not in scope: data constructor `Writer'     Perhaps you meant `WriterT' (imported from Control.Monad.Writer) Failed, modules loaded: none. 

I examined the type by ":info" command:

Prelude Control.Monad.Writer> :info Writer type Writer w = WriterT w Data.Functor.Identity.Identity                -- Defined in `Control.Monad.Trans.Writer.Lazy' 

From my point of view, this was supposed to be something like "newtype Writer w a ..." so I'm confused about how to feed the data constructor and get a Writer.

I guess it might be a version-related problem and my ghci version is 7.4.1

like image 758
Javran Avatar asked Jul 27 '12 08:07

Javran


People also ask

What is a writer Monad?

The Writer monad is a programming design pattern which makes it possible to compose functions which return their result values paired with a log string. The final result of a composed function yields both a value, and a concatenation of the logs from each component function application.

Is either a Monad Haskell?

Today I'll start with a simple observation: the Either type is a monad! For a long time, I used Either as if it were just a normal type with no special rules. But its monadic behavior allows us to chain together several computations with it with ease!

What is the reader Monad?

The Reader monad (also called the Environment monad). Represents a computation, which can read values from a shared environment, pass values from function to function, and execute sub-computations in a modified environment. Using Reader monad for such computations is often clearer and easier than using the State monad.

What is state Monad?

The state monad is a built in monad in Haskell that allows for chaining of a state variable (which may be arbitrarily complex) through a series of function calls, to simulate stateful code.


2 Answers

The package Control.Monad.Writer does not export the data constructor Writer. I guess this was different when LYAH was written.

Using the MonadWriter typeclass in ghci

Instead, you create writers using the writer function. For example, in a ghci session I can do

ghci> import Control.Monad.Writer ghci> let logNumber x = writer (x, ["Got number: " ++ show x]) 

Now logNumber is a function that creates writers. I can ask for its type:

ghci> :t logNumber logNumber :: (Show a, MonadWriter [String] m) => a -> m a 

Which tells me that the inferred type is not a function that returns a particular writer, but rather anything that implements the MonadWriter type class. I can now use it:

ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }     :: Writer [String] Int 

(Input actually entered all on one line). Here I've specified the type of multWithLog to be Writer [String] Int. Now I can run it:

ghci> runWriter multWithLog (15, ["Got number: 3","Got number: 5"]) 

And you see that we log all of the intermediate operations.

Why is the code written like this?

Why bother to create the MonadWriter type class at all? The reason is to do with monad transformers. As you correctly realised, the simplest way to implement Writer is as a newtype wrapper on top of a pair:

newtype Writer w a = Writer { runWriter :: (a,w) } 

You can declare a monad instance for this, and then write the function

tell :: Monoid w => w -> Writer w () 

which simply logs its input. Now suppose you want a monad that has logging capabilities, but also does something else - say it can read from an environment too. You'd implement this as

type RW r w a = ReaderT r (Writer w a) 

Now because the writer is inside the ReaderT monad transformer, if you want to log output you can't use tell w (because that only operates with unwrapped writers) but you have to use lift $ tell w, which "lifts" the tell function through the ReaderT so that it can access the inner writer monad. If you wanted two layers transformers (say you wanted to add error handling as well) then you'd need to use lift $ lift $ tell w. This quickly gets unwieldy.

Instead, by defining a type class we can make any monad transformer wrapper around a writer into an instance of writer itself. For example,

instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m) 

that is, if w is a monoid, and m is a MonadWriter w, then ReaderT r m is also a MonadWriter w. This means that we can use the tell function directly on the transformed monad, without having to bother with explicitly lifting it through the monad transformer.

like image 144
Chris Taylor Avatar answered Sep 24 '22 18:09

Chris Taylor


A function called "writer" is made available in lieu of a "Writer" constructor. Change:

logNumber x = Writer (x, ["Got number: " ++ show x])

to:

logNumber x = writer (x, ["Got number: " ++ show x])

like image 27
Marcus Avatar answered Sep 24 '22 18:09

Marcus