8

I am very much new to Haskell, and really impressed by the language's "architecture", but it still bothers me how monads can be pure.

As you have any sequence of instructions, it makes it an impure function, especially functions with I/O wouldn't be pure from any point of view.

Is it because Haskell assumes, like all pure functions, that IO function has a return value too, but in form of opcode or something? I am really confused.

Enlico
  • 12,203
  • 5
  • 28
  • 59
lycuid
  • 2,417
  • 1
  • 16
  • 26
  • Btw - where are you seeing this? Having the context would help in formulating an answer. – ErikR Sep 11 '16 at 16:38
  • well, IO functions time to execute and also having interaction with user, like _getLine_ which not always but tend to have side-effect. How can that be pure? – lycuid Sep 11 '16 at 17:43
  • 3
    The first step to understanding how I/O is managed is to recognize that `getLine` is *not* an I/O function. It is a constant with type `IO String`, which *represents* a program that can perform I/O. – chepner Sep 12 '16 at 02:29

5 Answers5

21

One way to think of this is that a value of type IO a is a "recipe", containing a list of instructions that if performed would have side effects. Constructing that "recipe" though, does not have any side effects. So a haskell program (whose type is IO ()) is basically a computation that constructs such a recipe. Importantly, the program does not execute any of the instructions in the recipe. When the recipe is completed the program terminates. Then the compiler (or interpreter) takes that recipe and executes it. But the code that the programmer wrote is not running anymore, so the instructions in the recipe are executed outside the scope of the program.

redneb
  • 16,685
  • 4
  • 31
  • 50
  • 1
    Simply, it's pure because (1) it will give the same output for the same input and (2) and when evaluated it does not have side-effects on other computations – Bassam Jan 18 '17 at 17:02
11

One way that "monads can be pure" is that they can represent pure expressions. I.e. in the List monad this:

do x <- xs
   return (x+1)

is the same as map (+1) xs.

Another way you can say "monads can be pure" is that Haskell distinguishes between creating a monadic computation and running the computation.

Creating a monadic computation is pure and doesn't involve any side effects. For instance, replicateM 3 foo will perform foo three times as in replicateM 3 (putStrLn "Hello, world"). Purity allows us to reason that replicateM 3 foo is the same as do { foo; foo; foo }. Note that this is true regardless of what kind of computation foo is - it could be a pure computation or one that involves some sort of effect.

Side effects only happen when you run the monadic computation. In the case of the IO monad this happens at run-time time when main is executed. Other monads have their own "run" functions, i.e. runReader for the Reader monad, runState for the State monad, etc.

ErikR
  • 50,049
  • 6
  • 66
  • 121
10

Monads are not considered pure or impure. They're totally unrelated concepts. Your title is kind of like asking how verbs are considered delicious.

"Monad" refers to a particular pattern of composition that can be implemented on types with certain higher-kinded type constructors. The entirety of the concept is tied up in the types of a couple operations and the rules for how those operations must interact with themselves and each other.

Few languages can express the concept usefully, since it's so abstract. The only relatively common language other than Haskell that can is Scala. And it's actually relatively common in Scala too, though they call it flatMap for some reason. Unsurprisingly, some types that support flatMap in Scala aren't pure. They support flatMap 100% correctly, and they aren't pure.

The concepts are just not correlated.

Now, all that said, I understand where you're coming from. Nearly every article on Haskell you see uses phrases like "the IO monad" or "uses monads to control effects" or other similar things. The thing is, every use of terminology like that is deeply misleading.

IO is a type. That's what's different from impure languages. IO operations are values of a particular type. That's what allows Haskell to stay principled (in some ways) about purity, even while interacting with the outside world. Values of a particular type are built to describe interactions with the outside world. Those values are pure, as described in the other answers.

So where does Monad fit into the whole thing? Well, IO values need to be combined together to build up more complex IO actions from simpler ones. And it turns out that they way they combine together is exactly the kind of composition the Monad interface describes. But so is combining lists with flatMap, or Option values with andThen.

The emphasis on Monad as something important, special, or interesting harms both the reputation of Haskell and its approachability for beginners. Especially since it's not important, special or interesting. The best comparison I can make is Iterator in Java. Yes, the language has some syntactic sugar for working with Monad/Iterator. No that doesn't come near implying that the language is unapproachable if you don't know the concept beforehand or that there's deep meaning involved that is necessary for entry into some super-secret society of enlightenment. When it comes down to it, neither idea is very deep or amazing. They're just very widely applicable, simple ideas that are easier to work with when you have a bit of syntactic sugar on hand.

Carl
  • 24,118
  • 4
  • 57
  • 79
  • _"Monad" refers to a particular pattern of composition that can be implemented on types with certain higher-kinded type constructors._ That is all there is to it! Explained in a single sentence. Thank you! –  Nov 04 '17 at 12:44
5

redbneb's answer is almost right, except that for Monads the two timelines are intermingled, which is their essence;

a Haskell computation does take place after the outside world has supplied some inputs, say, in a previous computation step; to construct the next recipe ⁄ "computation descriptions", which is then run in its turn. Otherwise it would not be a Monad, but an Applicative, which constructs its recipes ⁄ descriptions from components known ahead of time.

And lowly Functor itself already has the two timelines (which is its essence): IO a value describes an "outside world's" IO-computation "producing" an "inside" ⁄ pure a result.

Consider:

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

(written with monad comprehensions). Of course a Functor (Applicative / Monad / ...) can be pure as well; still there are two timelines ⁄ "worlds" there.

Few concrete 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:
~> (\a b c-> a*b) <$> [10,100] <*> [2,3] <*> [(),()]     -- it's applicative!
[20,20,30,30,200,200,300,300]

~> [r | x<-[10,100], y <- [2,3], r <- [x*y, x+y]]                -- and even this
~> (\a b c-> c (a*b,a+b)) <$> [10,100] <*> [2,3] <*> [fst,snd]   -- as well
~> (\a b c-> c a b)       <$> [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]                  

Essentially-monadic computations are built of steps which can't be constructed ahead of the combined computation's run-time, because what recipe to construct is determined by the value resulting from a previously computed value -- value, produced by the recipe's computation when it is actually performed.

The following image might also prove illuminating:

pipes image

Will Ness
  • 62,652
  • 8
  • 86
  • 167
  • I would probably phrase it a bit differently, because my mental notion of a recipe can be monadic: "Add salt to taste." – dfeuer Sep 22 '16 at 19:44
  • @dfeuer I didn't quite get your point. My point is that adding salt is not monadic. If `f` is "adding" and `a` is "salt", then `f a` is "adding salt" already, before we talk of Monads or Functors even. – Will Ness Sep 22 '16 at 21:11
  • You missed the key "to taste". You taste the dish to decide if it needs more salt. Then you add salt iteratively, until it no longer needs more salt. – dfeuer Sep 22 '16 at 21:16
  • @dfeuer ah, yes, this process *is* essentially-monadic. But you could also just add pre-measured amount, according to your cook-book recipe. :) There's the usual mix-up here; that's why I prefer "computation description" (for `f a` things), and "c.d. constructors" (for functions `a -> f b`). Your "recipe" refers to the latter; redneb's "recipe" to the former. – Will Ness Sep 22 '16 at 21:22
0

Adding an answer just because what I'm sharing helped me a lot.

One of (if not the) most notable Monads is IO, which allows an interaction with the "real world".

Is it impure?

No, it's pure: it takes the "real world" and spits out two outputs,

  • one that you're gonna use "intentionally",
  • and the other one, the possibly modified "real world", which will sit there waiting for the next monadic action to take it in input.

A more extensive explanation with a very clear example is here.

And this is another useful (but also funny!) way of picturing functors, applicative functors, and monands in your mind.

Enlico
  • 12,203
  • 5
  • 28
  • 59