Let Monad be more violent_Haskell notes 11

I wrote that I
first came into contact with IO Monad, and then I learned about Maybe Monad and List Monad. In fact, there are many Monads (such as Writer Monad, Reader Monad, State Monad, etc.) located in the mtl package, which can be viewed through the ghc-pkg command :


$ ghc-pkg list | grep mtl
mtl-2.2.1

PSHaskell Platform includes mtl package by default, no need to install manually

1. Writer Monad
tracking execution process
When understanding recursive algorithms, there is a strong demand, that is, to record the intermediate process. This was how it was done at the time:


import Debug.Trace
d f = trace ("{" ++ show f ++ "}") f

Add the log via trace :: String -> a -> a:


When called, trace outputs the string in its first argument, before returning the second argument as its result.

Accept a string and value, print out the string, and return the input value as it is, for example:


> x `add` y = trace (show x ++ " + " ++ show y) (x + y)
> add 3 $ add 1 2
1 + 2
3 + 3
6

The execution process is successfully traced, but the source code needs to be modified. It is too troublesome to change each function to a version with logs, so use the tool function d to do it (whatever you want to know):


> d (1 + 2) + 3
{3}
6

Take the classic Haskell quick sort as an example:


quickSort [] = []
quickSort (x:xs) = quickSort ltX ++ [x] ++ quickSort gtX
  where ltX = [a | a <- xs, a <= x]
        gtX = [a | a <- xs, a > x]

Add a log, see the processing process on the left (d ltX):


quickSortWithLog [] = []
quickSortWithLog (x:xs) = quickSortWithLog (d ltX) ++ [x] ++ quickSortWithLog gtX
  where ltX = [a | a <- xs, a <= x]
        gtX = [a | a <- xs, a > x]

Try it out:


> quickSortWithLog [9, 0, 8, 10, -5, 2, 13, 7]
{[0,8,-5,2,7]}
{[-5]}
{[]}
[-5,0{[2,7]}
{[]}
,2{[]}
,7,8,9{[]}
,10{[]}
,13]

According to the log, the left side of the first trip is [0,8,-5,2,7] (pivot is 9), and going on is [-5] (pivot is 0), and then on the left is [] (pivot is -5), the first trip on the other side of (0) is [2,7] on the left, and the left is [] when you continue. The left side of the original array is processed, and the right side is similar, so I won’t repeat it

Can barely solve the problem, but there are several shortcomings:

The log output is mixed in the results, the log does not look very intuitive

The log will affect the original result output, lack of isolation

It can only be printed out, there is no way to collect it for further processing, not flexible enough

So, if you want to track the execution process, is there a more elegant way?


Can Writer automatically maintain an operation log while computing?

If you regard the additional log information as context, it seems to have something to do with Monad. For example, you can automatically collect logs (maintain this context) while the value is involved in the calculation.

This is the origin of Writer:

Writer adds a value-added context, just like log

Writer allows us to collect all log records while calculating, and integrate a log and attach it to the result

Writer looks like this:


type Writer w = WriterT w Identity
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

From the perspective of the type declaration, Writer is a wrapper for tuples ((a, w)), and m is designated as Identity:


newtype Identity a = Identity { runIdentity :: a }
instance Applicative Identity where
  pure     = Identity
instance Monad Identity where
  m >>= k  = k (runIdentity m)

It seems useless, take a closer look: a packaging type called Identity is declared, and Monad is also implemented. The behavior of return is to wrap the given value, and the behavior of >>= is to apply the right side to the value wrapped on the left. function. I still haven't found any use... In fact, it is equivalent to id :: a -> a in the Monad world, which can wrap a value into a Monad to participate in the calculation. In addition, it does nothing. In terms of application scenarios, it is like id The same, sometimes you need a Monad that does nothing (just like sometimes you need a function that does nothing)


Identity allows us to define just monad transformers and then define their corresponding monads just as SomeT Identity.

PS For more discussion on Identity, see Why is Identity monad useful?

Next, look at the Monad implementation of WriterT:


instance (Monoid w, Monad m) => Monad (WriterT w m) where
    return a = writer (a, mempty)
    m >>= k  = WriterT $ do
        ~(a, w)  <- runWriterT m
        ~(b, w') <- runWriterT (k a)
        return (b, w `mappend` w')
    fail msg = WriterT $ fail msg

writer :: (Monad m) => (a, w) -> WriterT w m a
writer = WriterT . return

Where a is the type of the value, and w is the type of the additional Monoid. From the perspective of Monad implementation, take out the value a and additional information w from the left, apply the function on the right to a, and take out the value b and additional information w'from the result, the result value is b, and the additional information is w mappendw', Finally wrap the result with return to return the value of type m as a parameter of the WriterT value constructor

Note that the key point is to do w mappendw'for additional information while calculating the value , so as to preserve the log context and achieve automatic maintenance of the operation log

If m = Identity (Writer is defined in this way), the specific process is equivalent to:


(WriterT (Identity (a, w))) >>= f = let (WriterT (Identity (b, w'))) = f a in WriterT (Identity (b, w `mappend` w'))

The ~ in PS~(a, w) means lazy pattern matching (see Haskell/Laziness | Lazy pattern matching for details):


prepending a pattern with a tilde sign delays the evaluation of the value until the component parts are actually used. But you run the risk that the value might not match the pattern — you’re telling the compiler ‘Trust me, I know it’ll work out’. (If it turns out it doesn’t match the pattern, you get a runtime error.)

Try it out:


> runWriterT $ (return 1 :: WriterT String Identity Int)
Identity (1,"")
> let (WriterT (Identity (a, w))) = WriterT (Identity (1,"")) in (a, w)
(1,"")
> (WriterT (Identity (1,"abc")) :: WriterT String Identity Int) >>= \a -> return (a + 1)
WriterT (Identity (2,"abc"))

Writer does not directly expose the value constructor, but Writer can be constructed through the writer function, for example:


> writer (1,"abc") :: Writer String Int
WriterT (Identity (1,"abc"))

Furthermore, it can be described in a clearer do notation:


do {
  a <- writer (1, "one")
  b <- writer (2, "two")
  return (a + b)
} :: Writer String Int

-- 得到的结果
WriterT (Identity (3,"onetwo"))

The logs are glued together, use an array instead:


do {
  a <- writer (1, ["one"])
  b <- writer (2, ["two"])
  return (a + b)
} :: Writer [String] Int

-- 得到的结果
WriterT (Identity (3,["one","two"]))

The calculation results and logs can be separated from it:


> fst . runWriter $ WriterT (Identity (3,["one","two"]))
3
> snd . runWriter $ WriterT (Identity (3,["one","two"]))
["one","two"]
> mapM_ putStrLn $ snd . runWriter $ WriterT (Identity (3,["one","two"]))
one
two

The defects mentioned above seem to be solved perfectly. There is another question. What if you only want to insert an irrelevant log?

of course can. tell can be used to insert additional information without a value:


tell :: MonadWriter w m => w -> m ()

Similar to print in the I/O scenario:


print :: Show a => a -> IO ()

The effect is also
the same, without a value, only records a piece of information, such as Writer:


logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, [show x])

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

The operands and operators can be recorded in the form of similar postfix expressions:


> multWithLog
WriterT (Identity (15,["3","5","*"]))

The specific process is equivalent to:


> writer (3, ["3"]) >>= \a -> (writer (5, ["5"])) >>= \b -> (writer ((), ["*"])) >> return (a * b) :: Writer [String] Int
WriterT (Identity (15,["3","5","*"]))

Looking back at tracking the execution process, Writer's solution is to add a log context to the values ​​involved in the calculation, and continue to maintain this log during the calculation process (through the internal mappend), so that the context of the calculation result is complete Operation log:

We just rewrite the ordinary value into Monadic value, and rely on >>= and Writer to help us handle everything.

So if you want to turn a normal function into a version with logs, just pack the parameters (and constants in the operation) into Writer. On the

Difference list,

we use List to hold logs, which is vaguely uneasy.

Because the ++ operation of List is right-joined by default (that is, inserted into the head of the List), it is more efficient. If you frequently insert to the end of the List, you need to traverse and build the List on the left each time, which is very inefficient. So, is there a more efficient List?

Yes, it is called Difference list, which can perform efficient append operation. The realization idea is very clever:

First, convert each List into a function:


-- [1,2,3]
\xs -> [1,2,3] ++ xs
-- []
\xs -> [] ++ xs

Note: The key point is that only List is prepend in the function body, which ensures the efficiency of ++ operation (the head insertion efficiency is very high)

Naturally, the append operation of List becomes a function combination:


f = \xs -> "dog" ++ xs
g = \xs -> "meat" ++ xs
f `append` g = \xs -> f (g xs)
-- 展开f `append` g,得到
\xs -> "dog" ++ ("meat" ++ xs)

Pass an empty List to the Difference list (a function that accepts a List parameter) to get the result List:

> f `append` g $ []
"dogmeat"

So, define DiffList like this:


newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }

toDiffList xs = DiffList (xs++)
fromDiffList (DiffList f)
]

Then realize Monoid:


instance Monoid (DiffList a) where
  mempty = DiffList (\xs -> [] ++ xs)
  (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))

Try it out:


> fromDiffList $ (toDiffList [1, 2, 3]) `mappend` (toDiffList [3, 2, 1])
[1,2,3,3,2,1]

So, what is the performance difference? Simply test:


countdown n = if (n == 0) then do {tell ["0"]} else do {countdown (n - 1); tell [show n]} :: Writer [String] ()
countdown' n = if (n == 0) then do {tell (toDiffList ["0"])} else do {countdown' (n - 1); tell (toDiffList [show n])} :: Writer (DiffList String) ()

In the countdown scenario, Writer is used to record each number in the countdown process. The difference is that countdown uses List to hold logs, while countdown uses DiffList

Mostly for a while, such as 500,000 counts:


> mapM_ putStrLn . snd . runWriter $ countdown 500000
> mapM_ putStrLn . fromDiffList . snd . runWriter $ countdown' 500000

In terms of efficiency visible to the naked eye, countdown runs slower and slower, countdown' always outputs smoothly

PS more scientific test method, see Performance | 5 Benchmarking libraries

For the complete implementation of PSDiffList, see spl/dlist

PS In addition, Haskell Platform does not come with a dlist package by default (so there is no built-in DiffList by default), you need to install it manually, see the beginning of this article

2.
Reader Monad Reader Monad is actually Function Monad, function is also Monad, how to understand this?

A function can also be thought of as containing a context. This context means that we are expecting a certain value, which has not yet appeared, but we know that we will use it as a function parameter and call the function to get the result.

That is to say (->) r, I have known before that it is a Functor and an Applicative. It turned out to be Monad, and its specific implementation is as follows:


instance Monad ((->) r) where
  f >>= k = \ r -> k (f r) r

There is no additional implementation of return, so it is pure Applicative:


instance Applicative ((->) a) where
  pure = const

const                   :: a -> b -> a
const x _               =  x

Accepts an arbitrary value (x) and returns a function (_ -> x) that accepts a parameter, ignores and returns the previously passed arbitrary value. This is done to wrap a value into the function context so that it can participate in function operations:

The only way for a function to be a certain fixed value is to make it completely ignore its parameters.

= From the implementation point of view, a new function will be generated (\ r -> k (fr) r), which accepts a parameter (r), which will be passed to the monadic value on the left (also a function, f), Then pass the return value (fr) to the function (k) on the right, return a monadic value (still a function, k (fr)), accept the parameter (r), and finally return a monadic value

PS It seems strange to pass r as a parameter to f. This is because f is a monadic value and has a context (a function). To get the value out of the function context, it must be fed with parameters. Similarly for k (fr), feed the value taken from f to k, return something with a function context, and finally feed the parameter r to it to get the final result

Okay, function is now Monad, what's the use of it?


palyAGame x = do
  f <- (/0.5) . (subtract 3.9343) . (*5) . (+52.8)
  g <- (*10)
  return (f - g)

Think of a number in mind, use it to add 52.8, then multiply by 5, then subtract 3.9343, then divide by 0.5, and finally subtract ten times the number in mind

Looking at the translation process, we divide the calculation formula into two parts related to x, and finally do subtraction. Try it out first:


> palyAGame 201807 01
520.1314

Note that in addition to x, we also input one more parameter, because the result is wrapped in return _ -> x, so you need to fill in any parameter to get the result

Recall the actual role of Function Monad:

Glue all the functions together to make a big function, and then feed the parameters of this function to all of the functions, which means taking out their future values

PS "Take their future value" refers to the final f-g, naughty description

In fact, a more scientific description is this:


The Reader monad (also called the Environment monad). Represents a computation, which can read values from a shared environment, pass values from function to function, and execute sub-computations in a modified environment.

Among them, the shared environment refers to Maintaining variable bindings, that is, every monadic value in the do block shares the parameters of this large function. The meaning of passing values ​​between functions is similar to "taking out their future values", as for tampering Sub-calculations in the past environment may refer to application scenarios such as dependency injection (see What is the purpose of the Reader Monad?)

PS can read values ​​from the shared environment, which is why it is called Reader Monad

3.
In addition to log tracking and sharing environment, State Monad also has the most common problem of state maintenance

However, there are some areas where problems are fundamentally dependent on changing states over time. Although we can also write such programs in Haskell, sometimes it is quite painful to write. This is why Haskell added the State Monad feature. This allows us to easily handle stateful issues in Haskell, and keep other parts of the program pure.

This is the meaning of State Monad. I want to make state maintenance easier without affecting other pure parts.

From an implementation perspective, State Monad is a function that accepts a state, returns a value and a new state


s -> (a,s)
-- 即
state -> (result, newState)

Similar to Writer Monad, the result value is separated from the additional information attached to the context (here, newState) and organized by two tuples

The specific implementation is as follows:


type State s = StateT s Identity
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }

instance (Monad m) => Monad (StateT s m) where
  return a = StateT $ \ s -> return (a, s)
  m >>= k  = StateT $ \ s -> do
      ~(a, s') <- runStateT m s
      runStateT (k a) s'
  fail str = StateT $ \ _ -> fail str

return puts the received value into a state operation function of s -> (a,s), and then wraps it into StateT

= Take out the state operation function from the left, pass in s to take out the new state s'and the calculation result a, then apply the function on the right to the calculation result a, and get a monadic value, and then take out the state operation function inside through runStateT , Apply to the new state s', get the (a, s) two-tuple and return. In this way, the type of lambda is the standard s -> (a, s), and finally, plug in StateT to construct a new monadic value

State Monad can make state maintenance operations more succinctly expressed, then, to what extent can this thing simplify state maintenance operations? Let’s look at the random number example

Random Numbers and State Monad
As far as scenarios are concerned, random numbers need to maintain state (random number seed), which is very suitable for processing with State Monad

Specifically, in the random number scenario before, random numbers were generated by changing different random number seeds to the random function:


random :: (Random a, RandomGen g) => g -> (a, g)

E.g:


> random (mkStdGen 7) :: (Bool, StdGen)
(True,320112 40692)

To generate 3 random numbers, the most direct method is:


random3'' = let (r1, g1) = random (mkStdGen 7); (r2, g2) = random g1; (r3, g3) = random g2 in [(r1, g1), (r2, g2), (r3, g3)] :: [(Bool, StdGen)]
> random3''
[(True,320112 40692),(False,2071543753 1655838864),(True,33684305 2103410263)]

Of course, it can be encapsulated slightly more elegantly:


random3 i = collectNext $ collectNext $ [random $ mkStdGen i]
  where collectNext xs@((i, g):_) = [random g] ++ xs

> reverse $ random3 7 :: [(Bool, StdGen)]
[(True,320112 40692),(False,2071543753 1655838864),(True,33684305 2103410263)]

It looks more comfortable, but it still feels troublesome. Switch to State Monad:


randomSt :: (RandomGen g, Random a) => State g a
randomSt = state random

threeCoins :: State StdGen (Bool,Bool,Bool)
threeCoins = do
  a <- randomSt
  b <- randomSt
  c <- randomSt
  return (a,b,c)

It looks quite elegant, the random function just satisfies the form of s -> (a, s), so just throw it to state :: MonadState sm => (s -> (a, s)) -> ma to construct the State Monad value. . Try it out:


> runState threeCoins (mkStdGen 7)
((True,False,True),33684305 2103410263)

The state s in the result (a, s) is the fourth random number seed (counting the incoming mkStdGen 7), because this seed is the latest state (the rest of the intermediate states are lost)

Yes, Moand simplifies a general scenario of state maintenance. State Monad helps us automatically complete the maintenance of the intermediate state, making everything as simple as possible

Four. Error Monad
Finally, exception handling is also an important scenario, which can also be simplified with the help of Monad


Building computations from sequences of functions that may fail or using exception handling to structure error handling.

We already know that Maybe is a Monad, which can be used to express calculations that may produce errors. What about Either? Is it okay?

of course. In fact, Either is an instance of Error Monad (also called Exception monad):


class (Monad m) => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

instance MonadError e (Either e) where
    throwError             = Left
    Left  l `catchError` h = h l
    Right r `catchError` _ = Right r

(From Control.Monad.Except)

PS Note that Control.Monad.Error and Control.Monad.Trans.Error are outdated, it is recommended to use Control.Monad.Except, see Control.Monad.Error for details

There is nothing to say about throwError. It is agreed that Left x represents an error (Right x represents a normal result). CatchError can be used to catch the error. If no error occurs, just do nothing. So the general pattern is this:


do { action1; action2; action3 } `catchError` handler

E.g:


do {
  x <- (Left "error occurred")
  return x
} `catchError` error

Catch the error, and then throw it directly with error, so I get an error:


*** Exception: error occurred
上面do block中的操作实际上依赖的是Either自身的Monad实现:

instance Monad (Either e) where
    Left  l >>= _ = Left l
    Right r >>= k = k r
等价于:

> ((Left "error occurred") >>= (\x -> return x) :: Either String Int) `catchError` error
*** Exception: error occurred
> ((throwError "error occurred") >>= (\x -> return x) :: Either String Int) `catchError` error
*** Exception: error occurred

In other words, Error Monad only implements additional throwError and catchError for those types that can express errors (such as Either, Maybe), without making intrusive modifications, but with these two behaviors, we can indeed handle errors gracefully , This is different from the Monads introduced above

In addition to Either, another important instance of MonadError is ExceptT (of course, there are more than these 2):


instance Monad m => MonadError e (ExceptT e m) where
    throwError = ExceptT.throwE
    catchError = ExceptT.catchE

After wrapping, you can use the throw and catch defined on ExceptT, so ExceptT can add error handling capabilities to other Monads. The implementation is as follows:


newtype ExceptT e m a = ExceptT (m (Either e a))

runExceptT :: ExceptT e m a -> m (Either e a)
runExceptT (ExceptT m) = m

throwE :: (Monad m) => e -> ExceptT e m a
throwE = ExceptT . return . Left

catchE :: (Monad m) =>
    ExceptT e m a               -- ^ the inner computation
    -> (e -> ExceptT e' m a)    -- ^ a handler for exceptions in the inner
                                -- computation
    -> ExceptT e' m a
m `catchE` h = ExceptT $ do
    a <- runExceptT m
    case a of
        Left  l -> runExceptT (h l)
        Right r -> return (Right r)

In fact, it is to wrap the value of other Monad (a) into Either, and add the exception information (e), while ensuring that the Monad type is correct (still m)

ThrowE converts the error message into Either with Left, then wraps it into the desired Monad with return, and finally plugs it into ExceptT to construct the ExceptT value

catchE takes out the left Either through runExceptT to see if there is an error, and then decide whether to throw it to the right handler

All figured out, now try to add exception handling to I/O operations:


getString :: ExceptT String IO String
getString = do {
  line <- liftIO getLine;
  if (null line) then
    throwError "empty input"
  else
    return line
} `catchError` (\e -> return "Error occurred, use default string")

safeIO = do
  -- 放心用Right匹配,因为getString有错误处理
  (Right line) <- runExceptT getString
  putStrLn line

Note that liftIO :: MonadIO m => IO a -> ma is used to raise IO to the required Monad context (ExceptT in the above example):

Lift a computation from the IO monad.

And runExceptT is used to take out the packaged in Except, for example:


> runExceptT (liftIO getLine :: ExceptT String IO String)
aaa
Right "aaa"

Try it out:

> safeIO

Error occurred, use default string
> safeIO
abc
abc

As expected, if the input is illegal, use the default string

PS In addition, Except is also defined on the basis of ExceptT:


type Except e = ExceptT e Identity

except :: Either e a -> Except e a
except m = ExceptT (Identity m)
runExcept :: Except e a -> Either e a
runExcept (ExceptT m) = runIdentity m

See Control.Monad.Trans.Except for details

5. The charm of
Monad Monad can give calculation some additional capabilities, such as:

Writer Monad: can convert the function into a log version, used to track the execution process, or add additional information to the data transformation

Reader Monad: enables a series of functions to work together in a controllable shared environment, such as reading parameters from this environment, reading the results of other functions, etc.

State Monad: can automatically maintain the state, suitable for scenarios that need to maintain the state, such as generating a series of random numbers

Error Monad: Provides an error handling mechanism, which can easily make calculations more secure

The significance of Monad is to abstract common patterns from these common scenarios to simplify operations, such as state maintenance, log collection, etc., can be automatically completed by Monad

From the point of view of use, just use Monad package (yes, it's that simple), you can get additional capabilities, this is the charm of Monad

Reference material
Control.Monad.Reader

Control.Monad.Error

Control.Monad.Except

Contact ayqy

Guess you like

Origin blog.51cto.com/15080030/2591201