📜 ⬆️ ⬇️

Three useful monads

Warning: before reading the text below, you should already have an idea of ​​what monads are. If this is not the case, then read this post before!

Before us function half :


And we can apply it several times:
 half . half $ 8 => 2 

')
Everything works as expected. But you decided that it would be nice to have a log of what is happening with this function:


 half x = (x `div` 2, "    " ++ (show x) ++ "!") 


Well, great. But what if you now want to apply half few times?
 half . half $ 8 


Here is what we would like to happen:


Spoiler : not automatically done. We'll have to paint everything handles:
 finalValue = (val2, log1 ++ log2) where (val1, log1) = half 8 (val2, log2) = half val1 


Ugh! This is not a drop like laconic
 half . half $ 8 


And what if you still have functions that have a log? This scheme suggests itself: for each function that returns, together with the value of the log, we would like to combine these logs. This is a side effect, and no one is as strong in side effects as monads!

Monad Writer



The monad Writer appears on the white horse
Monad Writer is a cool personality. “Don't worry, man, I'll process your logs,” she says. - “Go back to your clean code and squeeze the maximum for anything from Zeppelin!”

Each Writer instance has a log and a return value:


 data Writer wa = Writer { runWriter :: (a, w) } 


Writer allows us to write code like
 half 8 >>= half 


Well, or you can use the <=< function, which is the composition of functions for monads, to get:
 half <=< half $ 8 

which is damn close to half . half $ 8 half . half $ 8 . Cool!

You can use tell to write something to the log. And return will pass this value to Writer. Here is our updated half function:
 half :: Int -> Writer String Int half x = do tell ("I just halved " ++ (show x) ++ "!") return (x `div` 2) 


It returns Writer :


And we can use runWriter to extract the value from it:


 runWriter $ half 8 => (4, "I just halved 8!") 


But the coolest thing is that now we can link the half calls to a chain using >>= :
 runWriter $ half 8 >>= half => (2, "I just halved 8!I just halved 4!") 


Here is what happens:


Magically >>= knows how to combine the two Writers, so we don’t need to write any tedious code ourselves! Here is the full definition >>= :


We wrote the exact same template code earlier, but now we have assumed this responsibility >>= . Cool We also use the return function, which takes a value and places it in the monad:


 return val = Writer (val, "") 


( Note : this definition is almost correct. In fact, the Writer monad allows us to use any Monoid as a log, not just strings. I simplified it slightly.)

Thank you, monad Writer!

Monad Reader


Suppose you want to pass some settings to a variety of functions. Just use the Reader monad:


It allows you to transfer your value to all functions, hiding the transmission mechanism itself behind the scenes. For example:
 greeter :: Reader String String greeter = do name <- ask return ("hello, " ++ name ++ "!") 


greeter returns the reader monad:


Here is its definition:
 data Reader ra = Reader { runReader :: r -> a } 


Reader has always been a turncoat. Dark horse. Reader is different because it allows us to process a function, which is confusing when considering it. But you and I understand that we can use runReader to extract this function:


And we can transfer a certain state to this function, which is then used in greeter :


 runReader greeter $ "adit" => "hello, adit!" 


this way, when you use >>= , you should get a Reader back. When you pass a state to it, it will be transferred to all functions contained in this monad.


 m >>= k = Reader $ \r -> runReader (k (runReader mr)) r 


Reader has always been somewhat comprehensive. This is generally his best quality.
return puts the value in the Reader :


 return a = Reader $ \_ -> a 


And, finally, ask gives you back the state you passed:
 ask = Reader $ \x -> x 


Want to spend more time with Reader? Cut punk rock and take a look at a longer example .

Monad State


The Monad State is the more impressionable best friend of the Reader monad:


It is exactly the same as the Monad Reader, but with its help you can not only read but also write!
Here is the definition of State :
 State sa = State { runState :: s -> (a, s) } 




You can get a state with get or change it with put . Here is an example:
 greeter :: State String String greeter = do name <- get put "tintin" return ("hello, " ++ name ++ "!") runState greeter $ "adit" => ("hello, adit!", "tintin") 


Nicely! If the Reader was with the character “you-me-not-change”, then State, on the contrary, tends to relations and is ready to change itself.
The definition of a State monad looks very similar to the definition of a Reader monad:
return :


 return a = State $ \s -> (a, s) 


>>= :


 m >>= k = State $ \s -> let (a, s') = runState ms in runState (ka) s' 


Conclusion




Writer. Reader. State. Today you have added three powerful weapons to your Haskell arsenal. Use them wisely.

From the translator: I will be infinitely grateful for any comments in PM that will help improve the quality of this translation.

Source: https://habr.com/ru/post/184722/


All Articles