How are monads considered clean?

I am very new to Haskell and really impressed with the "architecture" of the language, but it still bothers me how monads can be clean.

Since you have any sequence of instructions, this makes it an unclean function, especially functions with I / O will not be clean from any point of view.

This is because Haskell accepts, like all pure functions, the IO function also has a return value, but in the form of an operation code or something else? I'm really confused.

+5
source share
4 answers

One way to think about this is that a value of type IO a is a "recipe" containing a list of instructions that will have side effects when executed. Build this "recipe", although it does not have any side effects. Thus, the haskell program (whose type is IO () ) is basically a calculation that creates such a recipe. It is important to note that the program does not follow any instructions in the recipe. When the recipe is completed, the program ends. Then the compiler (or interpreter) takes this recipe and executes it. But the code written by the programmer no longer works, so the instructions in the recipe are executed outside the scope of the program.

+18
source

One of the ways that "monads can be pure" is that they can represent pure expressions. That is, in the list of "Monad":

 do x <- xs return (x+1) 

matches map (+1) xs .

Another way you can say, “Monads can be clean,” is that Haskell distinguishes between creating a monadic calculation and performing a calculation.

Creating a monadic calculation is clean and not associated with any side effects. For example, replicateM 3 foo will execute foo three times as in replicateM 3 (putStrLn "Hello, world") . Cleanliness allows us to reason that replicateM 3 foo same as do { foo; foo; foo } do { foo; foo; foo } do { foo; foo; foo } . Note that this is true no matter what the calculation of foo is - it can be a pure calculation or one that is associated with some kind of effect.

Side effects occur only when performing monadic calculations. In the case of the I / O monad, this happens at runtime when main is executed. Other monads have their own "running" functions, i.e. runReader for reader monad, runState for state monad, etc.

+9
source

Monads are not considered clean or unclean. These are completely unrelated concepts. Your name is like asking how verbs are considered tasty.

"Monad" refers to a specific composition structure that can be implemented on types with specific constructors of a higher type. The whole concept is tied to the types of operations of the pair and the rules of how these operations should interact with themselves and with each other.

Several languages ​​can express a concept with benefit, as it is so abstract. The only relatively common language besides Haskell could be Scala. And this is actually relatively common in Scala, although for some reason they call it flatMap . Unsurprisingly, some types supporting flatMap in Scala are not pure. They support flatMap 100% correctly and they are not clean.

The concepts are simply not correlated.

Now, all that said, I understand where you are from. Almost every article in Haskell that you see uses phrases such as “nun IO” or “uses monads to control effects” or other similar things. The fact is that any use of such terminology is deeply misleading.

IO - type. This is different from unclean languages. I / O operations are values ​​of a specific type. This is what allows Haskell to remain principled (in some way) regarding purity, even interacting with the outside world. Values ​​of a specific type are constructed to describe interactions with the outside world. These values ​​are pure as described in other answers.

So where does the Monad fit into all this? Well, IO values ​​must be combined together to create more complex I / O actions from simpler ones. And it turns out that they combine with each other just such a composition that describes the Monad interface. But lists with flatMap or Option values ​​with andThen .

Emphasizing Monad as something important, special, or interesting harms Haskell's reputation and its ability for beginners. Moreover, it is not important, special or interesting. The best comparison I can do is Iterator in Java. Yes, the language has syntactic sugar for working with Monad / Iterator . No, that doesn’t come nearer, implying that language is impregnable if you do not know the concept in advance or that there is a deep meaning necessary to enter some kind of top-secret enlightenment society. When it comes to this, not a single idea is very deep or surprising. They are very widely used, simple ideas that are easier to work with when you have a little syntactic sugar at hand.

+7
source

redbneb's answer is almost right, except that for Monads the two time intervals are mixed, which is their essence;

Haskell’s calculation takes place after the outside world has provided some inputs, say, in the previous calculation step; build the next recipe and frasl; "computation descriptions", which are then run in turn. Otherwise, it would not be a Monad, but an application that builds its recipes and frasl; descriptions of pre-known components.

And at the very bottom of the Functor itself there are already two timelines (what is its essence): IO a value describes the "outside world" & frasl; future IO-computing, producing "inside" and frasl; net result a .

Consider:

  [fx | x <- xs] f <$> xs Functor [r | x<-xs,r<-[fx]] [yx | y <- f, x <- xs] f <*> xs Applicative [r | y<-f,x<-xs,r<-[yx]] [r | x <- xs, r <- fx] f =<< xs Monad [r | x<-xs,r<- fx ] 

(written using monads). Of course, the Functor (Applicative / Monad / ...) can also be clean; there are still two time frames & frasl; "worlds" there.

A few specific examples:

 ~> [x*2 | x<-[10,100]] ~> [r | x<-[10,100], r <- [x*2]] -- non-monadic [20,200] -- (*2) <$> [10,100] ~> [x*y | x<-[10,100], y <- [2,3]] ~> [r | x<-[10,100], y <- [2,3], r <- [x*y]] -- non-monadic [20,30,200,300] -- (*) <$> [10,100] <*> [2,3] ~> [r | x<-[10,100], y <- [2,3], r <- replicate 2 (x*y) ] ~> [r | x<-[10,100], y <- [2,3], r <- [x*y, x*y]] -- still non-monadic: ~> (\ab c-> a*b) <$> [10,100] <*> [2,3] <*> [(),()] -- it applicative! [20,20,30,30,200,200,300,300] ~> [r | x<-[10,100], y <- [2,3], r <- [x*y, x+y]] -- and even this ~> (\ab c-> c (a*b,a+b)) <$> [10,100] <*> [2,3] <*> [fst,snd] -- as well ~> (\ab c-> cab) <$> [10,100] <*> [2,3] <*> [(*),(+)] [20,12,30,13,200,102,300,103] ~> [r | x<-[10,100], y <- [2,3], r <- replicate y (x*y) ] -- only this is _essentially_ ~> [10,100] >>= \x-> [2,3] >>= \y -> replicate y (x*y) -- monadic !!!! [20,20,30,30,30,200,200,300,300,300] 

Phased-monadic calculations are built from steps that cannot be built before the combined calculation time, because which recipe to build is determined by the value obtained from the previously calculated cost-value created by the calculation recipe when it is actually executed.

The following image may also display backlight :

enter image description here

+2
source

All Articles