欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

haskell - few more monad - Writer Monad

程序员文章站 2024-01-10 13:49:09
...

We will going to examine more monad, which will makes us more easily with a more variety of problems. exploring a few monads more will also solidify our intuition for monads. 

 

to install the Monads, do the following. 

 

The monads that we'll be exploring are all part of the mtl package. A Haskell package is a collection of modules. The mtl package comes with the Haskell Platform, so you probably already have it. To check if you do, type ghc-pkg list in the command-line. This will show which Haskell packages you have installed and one of them should be mtl, followed by a version number.

 

Writer.. 

 

we we need writer.. 

For instance, we might want to equip our values with strings that explain what's going on, probably for debugging purposes. 

 

consider a function, which takes a number of bandits in a gang and tell us inf that's a big bang or not.. 

 

isBigGang :: Int -> Bool  
isBigGang x = x > 9  

 

it only gives  you a true or false, nothing else, youcan write as this , which wrap the result in a tuple such as this: 

isBigGang :: Int -> (Bool, String)  
isBigGang x = (x > 9, "Compared gang size to 9.")  

 

in the same vein, let's make a function that takes a value with an attached log, that is , (a, String) value and a function of type a -> (b, String) and feeds that value into the function. We'll call it applyLog.

applyLog :: (a,String) -> (a -> (b,String)) -> (b,String)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log ++ newLog)  

 and let's do some examples. 

 

ghci> (3, "Smallish gang.") `applyLog` isBigGang  
(False,"Smallish gang.Compared gang size to 9")  
ghci> (30, "A freaking platoon.") `applyLog` isBigGang  
(True,"A freaking platoon.Compared gang size to 9")  

 

 the Monoids to rescue

 

The log with string is not good enough to represent the logs, why has it to be string, can it not be somting else such as []? we can revise the signature as this below? 

applyLog :: (a,[c]) -> (a -> (b,[c])) -> (b,[c])  

 but can  you extends that to some other types? what if you want to that on the bytestring are monoids, as such, they are both instances of Monoid type class, which means that they implement the mappend fun ction, and for both lists and bytestring, mappend watch:

ghci> [1,2,3] `mappend` [4,5,6]  
[1,2,3,4,5,6]  
ghci> B.pack [99,104,105] `mappend` B.pack [104,117,97,104,117,97]  
Chunk "chi" (Chunk "huahua" Empty)  

 cool! Now our applyLog can work for any monoid, we have to chagne the type of the function to reflect it .as well as the implementation, here is the code. 

 

applyLog :: (Monoid m) => (a,m) -> (a -> (b,m)) -> (b,m)  
applyLog (x,log) f = let (y,newLog) = f x in (y,log `mappend` newLog) 
now, with the accompany value being some monoid value, we no longer have to think of hte value as the value and a log, but now we can think of it as a vlaue with accompanty monoid value. Let 's make method that take a food and sum the provices fo far. 

 

 

import Data.Monoid  
  
type Food = String  
type Price = Sum Int  
  
addDrink :: Food -> (Food,Price)  
addDrink "beans" = ("milk", Sum 25)  
addDrink "jerky" = ("whiskey", Sum 99)  
addDrink _ = ("beer", Sum 30)  
 

 

now, let's do some test 

 

ghci> ("beans", Sum 10) `applyLog` addDrink  
("milk",Sum {getSum = 35})  
ghci> ("jerky", Sum 25) `applyLog` addDrink  
("whiskey",Sum {getSum = 124})  
ghci> ("dogmeat", Sum 5) `applyLog` addDrink  
("beer",Sum {getSum = 35})  
 we can chan the call of the addDrink as below. 

 

 

ghci> ("dogmeat", Sum 5) `applyLog` addDrink `applyLog` addDrink  
("beer",Sum {getSum = 65})  
 

 

so, let's introduce the Writer type, 

 

 

newtype Writer w a = Writer { runWriter :: (a, w) }  
 and the we can make it instance of Monad . 

 

 

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')  
 y is the new result, and we use the mappend to combine the old monoid value with the new one (just like the Sum calles we see in the above applyLog method) 

 

 

if we run some test againt this definition of the writer, it gives back this: 

 

 

ghci> runWriter (return 3 :: Writer String Int)  
(3,"")  
ghci> runWriter (return 3 :: Writer (Sum Int) Int)  
(3,Sum {getSum = 0})  
ghci> runWriter (return 3 :: Writer (Product Int) Int)  
(3,Product {getProduct = 1})  
 

 

The do notation with Writer. 

 

let's see an example. 

 

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)  
 this logs all the operand of the the product, so we can get 
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5"])  
 
and in case we just want to insert some log information at some particular poin, then the tell method can be useful. an new example which has "tell" method 
multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    tell ["Gonna multiply these two"]  
    return (a*b)  
 
the result of running this is 
ghci> runWriter multWithLog  
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])  
 
remember, to use tell method, tell is part of the "MonadWriter"typeclass and in case of Writer, it takes a monoid vale. ike ["This is going on"] and creates a Writer value that presents the dummy value () as its result but has our desired monoid value attached.

Adding logging to programs. 

we will take a look at the Euclid's algorithm, which takes two numbers and compute the greatest common divisor, that is the biggest number that will divies both of them .. 
gcd' :: Int -> Int -> Int  
gcd' a b   
    | b == 0    = a  
    | otherwise = gcd' b (a `mod` b)  
 
but given some ability to check on the details of the algorithm give sthe developers some edges on what is going on inside the algorithm, so we will make a new gcd'
import Control.Monad.Writer  
  
gcd' :: Int -> Int -> Writer [String] Int  
gcd' a b  
    | b == 0 = do  
        tell ["Finished with " ++ show a]  
        return a  
    | otherwise = do  
        tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
        gcd' b (a `mod` b)  
 
now, when you write the Writer call on gcd' you might get something like this: 
Writer (a, ["Finished with " ++ show a])  
 what we get is a tuple (the first being the result and the second being the logs) you can do this 
ghci> fst $ runWriter (gcd' 8 3)  
1  
 and you can get the logs as this: 
ghci> mapM_ putStrLn $ snd $ runWriter (gcd' 8 3)  
8 mod 3 = 2  
3 mod 2 = 1  
2 mod 1 = 0  
Finished with 1 
 

Effeciency consideration 

in our gcd' impelmentaion, we have construct the list in such a way that it ends up like this: 
a ++ (b ++ (c ++ (d ++ (e ++ f))))  
but if the list is constructed like this:  
((((a ++ b) ++ c) ++ d) ++ e) ++ f  
 this association to the left instead of to the right, This is inefficient because every time it wants to add the right part to the left part, it has to construct the left part all the way from the beginning! 
this is a reverse log gcd implementation like this; 
import Control.Monad.Writer  
  
gcdReverse :: Int -> Int -> Writer [String] Int  
gcdReverse a b  
    | b == 0 = do  
        tell ["Finished with " ++ show a]  
        return a  
    | otherwise = do  
        result <- gcdReverse b (a `mod` b)  
        tell [show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b)]  
        return result  
 the result as shown by the following. 
ghci> mapM_ putStrLn $ snd $ runWriter (gcdReverse 8 3)  
Finished with 1  
2 mod 1 = 0  
3 mod 2 = 1  
8 mod 3 = 2  
 the final resut appears in the first statement. 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
相关标签: haskell