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 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 = 2the final resut appears in the first statement.
深入理解Monads在Haskell中的应用
本文详细介绍了如何安装并使用Monads,通过实例展示了Writer Monad的应用,包括如何将函数与日志记录结合使用,以及如何使用Monad Writer进行高效的数据处理。此外,文章还探讨了Monads的效率考虑及其实现细节。
836

被折叠的 条评论
为什么被折叠?



