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
The package
Control.Monad.Writerdoes not export the data constructorWriter. I guess this was different when LYAH was written.Using the MonadWriter typeclass in ghci
Instead, you create writers using the
writerfunction. For example, in a ghci session I can doNow
logNumberis a function that creates writers. I can ask for its type:Which tells me that the inferred type is not a function that returns a particular writer, but rather anything that implements the
MonadWritertype class. I can now use it:(Input actually entered all on one line). Here I’ve specified the type of
multWithLogto beWriter [String] Int. Now I can run it:And you see that we log all of the intermediate operations.
Why is the code written like this?
Why bother to create the
MonadWritertype class at all? The reason is to do with monad transformers. As you correctly realised, the simplest way to implementWriteris as a newtype wrapper on top of a pair:You can declare a monad instance for this, and then write the function
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
Now because the writer is inside the
ReaderTmonad transformer, if you want to log output you can’t usetell w(because that only operates with unwrapped writers) but you have to uselift $ tell w, which “lifts” thetellfunction through theReaderTso 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 uselift $ 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,
that is, if
wis a monoid, andmis aMonadWriter w, thenReaderT r mis also aMonadWriter w. This means that we can use thetellfunction directly on the transformed monad, without having to bother with explicitly lifting it through the monad transformer.