Haskell – 122 – altre monadi ancora – 3

Continuo da qui, copio qui, scrollare fino a “The Writer type”.

Il tipo Writer
Now that we’ve seen that a value with an attached monoid acts like a monadic value, let’s examine the Monad instance for types of such values. The Control.Monad.Writer module exports the Writer w a type along with its Monad instance and some useful functions for dealing with values of this type.

First, let’s examine the type itself. To attach a monoid to a value, we just need to put them together in a tuple. The Writer w a type is just a newtype wrapper for this. Its definition is very simple:

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

It’s wrapped in a newtype so that it can be made an instance of Monad and that its type is separate from a normal tuple. The a type parameter represents the type of the value and the w type parameter the type of the attached monoid value.

Its Monad instance is defined like so:

instance (Monoid w) => Monad (Writer w) where
  return x = Writer (x, mempty)
  (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')

First off, let’s examine >>=. Its implementation is essentially the same as applyLog, only now that our tuple is wrapped in the Writer newtype, we have to unwrap it when pattern matching. We take the value x and apply the function f to it. This gives us a Writer w a value and we use a let expression to pattern match on it. We present y as the new result and use mappend to combine the old monoid value with the new one. We pack that up with the result value in a tuple and then wrap that with the Writer constructor so that our result is a Writer value instead of just an unwrapped tuple.

So, what about return? It has to take a value and put it in a default minimal context that still presents that value as the result. So what would such a context be for Writer values? If we want the accompanying monoid value to affect other monoid values as little as possible, it makes sense to use mempty. mempty is used to present identity monoid values, such as "" and Sum 0 and empty bytestrings. Whenever we use mappend between mempty and some other monoid value, the result is that other monoid value. So if we use return to make a Writer value and then use >>= to feed that value to a function, the resulting monoid value will be only what the function returns. Let’s use return on the number 3 a bunch of times, only we’ll pair it with a different monoid every time:

Prelude> :set prompt "ghci> "
ghci> import Control.Monad.Writer
ghci> runWriter (return 3 :: Writer String Int)
ghci> runWriter (return 3 :: Writer (Sum Int) Int)
(3,Sum {getSum = 0})
ghci> runWriter (return 3 :: Writer (Product Int) Int)
(3,Product {getProduct = 1})

Because Writer doesn’t have a Show instance, we had to use runWriter to convert our Writer values to normal tuples that can be shown. For String, the monoid value is the empty string. With Sum, it’s 0, because if we add 0 to something, that something stays the same. For Product, the identity is 1.

The Writer instance doesn’t feature an implementation for fail, so if a pattern match fails in do notation, error is called.

Usare  do notationc con writer
Now that we have a Monad instance, we’re free to use do notation for Writer values. It’s handy for when we have a several Writer values and we want to do stuff with them. Like with other monads, we can treat them as normal values and the context gets taken for us. In this case, all the monoid values that come attached get mappended and so are reflected in the final result. Here’s a simple example of using do notation with Writer to multiply two numbers:

Qui c’è un problema, noto. La versione riportata da Miran è vecchia e non più supportata. Lo sa anche Miran che aggiorna, qui. Basta cambiare l’import, quello corretto è Control.Monad.Writer invece del vecchio Control.Monad.


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)

logNumber takes a number and makes a Writer value out of it. For the monoid, we use a list of strings and we equip the number with a singleton list that just says that we have that number. multWithLog is a Writer value which multiplies 3 and 5 and makes sure that their attached logs get included in the final log. We use return to present a*b as the result. Because return just takes something and puts it in a minimal context, we can be sure that it won’t add anything to the log. Here’s what we see if we run this:

Prelude> :l mwl
[1 of 1] Compiling Main             ( mwl.hs, interpreted )
Ok, modules loaded: Main.
*Main> multWithLog
WriterT (Identity (15,["Got number: 3","Got number: 5"]))

Sometimes we just want some monoid value to be included at some particular point. For this, the tell function is useful. It’s part of the MonadWriter type class and in the case of Writer it takes a monoid value, like ["This is going on"] and creates a Writer value that presents the dummy value () as its result but has our desired monoid value attached. When we have a monadic value that has () as its result, we don’t bind it to a variable. Here’s multWithLog but with some extra reporting included:

multWithLog :: Writer [String] Int
multWithLog = do
  a <- logNumber 3
  b <- logNumber 5
  tell ["Gonna multiply these two"]
  return (a*b)

It’s important that return (a*b) is the last line, because the result of the last line in a do expression is the result of the whole do expression. Had we put tell as the last line, () would have been the result of this do expression. We’d lose the result of the multiplication. However, the log would be the same. Here is this in action:

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

Pausa, devo recuperare per l’errore di aggiornamento 😐


Posta un commento o usa questo indirizzo per il trackback.



Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d blogger hanno fatto clic su Mi Piace per questo: