haskell - few more monad - Writer Monad
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"])
multWithLog :: Writer [String] Int multWithLog = do a <- logNumber 3 b <- logNumber 5 tell ["Gonna multiply these two"] return (a*b)
ghci> runWriter multWithLog (15,["Got number: 3","Got number: 5","Gonna multiply these two"])
Adding logging to programs.
gcd' :: Int -> Int -> Int gcd' a b | b == 0 = a | otherwise = gcd' b (a `mod` b)
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)
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) 1and 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
a ++ (b ++ (c ++ (d ++ (e ++ f))))but if the list is constructed like this:
((((a ++ b) ++ c) ++ d) ++ e) ++ fthis 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!
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 resultthe 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 = 2the final resut appears in the first statement.