383

In my humble opinion the answers to the famous question "What is a monad?", especially the most voted ones, try to explain what is a monad without clearly explaining why monads are really necessary. Can they be explained as the solution to a problem?

Community
  • 1
  • 1
cibercitizen1
  • 19,104
  • 14
  • 65
  • 90
  • 7
    http://cs.coloradocollege.edu/~bylvisaker/MonadMotivation/ – antoyo Jan 25 '15 at 18:05
  • 2
    http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html – user3237465 Jan 26 '15 at 00:29
  • 4
    What research have you already done? Where have you looked? What resources have you found? [We expect you to do a significant amount of research before asking, and show us in the question what research you've done](https://meta.stackoverflow.com/q/261592/781723). There are many resources that try to explain the motivation for resources -- if you haven't found any at all, you might need to do a little more research. If you've found some but they didn't help you, it would make this a better question if you explained what you'd found and why specifically they didn't work for you. – D.W. Jan 26 '15 at 22:42
  • 8
    This is definitely a better fit for Programmers.StackExchange and not a good fit for StackOverflow. I'd vote to migrate if I could, but I can't. =( – jpmc26 Jan 26 '15 at 22:42
  • 1
    Because there is syntactic sugar associated with them. – user253751 Jan 27 '15 at 06:42
  • Since you [posted related to this on Meta.SE](http://meta.stackexchange.com/questions/247653/are-moderators-expert-in-every-subject), please don't continue to 'argue' about it here. – Andrew Barber Jan 27 '15 at 19:40
  • 3
    @jpmc26 Most likely it would get closed there as "primarily opinion-based"; here it at least stands a chance (as shown by the huge number of upvotes, rapid reopen yesterday, and no more close votes yet) – Izkata Jan 28 '15 at 14:31
  • For future people coming here, take a look at this great lecture: https://www.youtube.com/watch?v=re96UgMk6GQ. You can start at 31:47 (and watch ~10-15 minutes), but I would recommend watching the whole thing as it is a great lecture. – xilpex Aug 26 '20 at 22:36

8 Answers8

601

Why do we need monads?

  1. We want to program only using functions. ("functional programming (FP)" after all).
  2. Then, we have a first big problem. This is a program:

    f(x) = 2 * x

    g(x,y) = x / y

    How can we say what is to be executed first? How can we form an ordered sequence of functions (i.e. a program) using no more than functions?

    Solution: compose functions. If you want first g and then f, just write f(g(x,y)). This way, "the program" is a function as well: main = f(g(x,y)). OK, but ...

  3. More problems: some functions might fail (i.e. g(2,0), divide by 0). We have no "exceptions" in FP (an exception is not a function). How do we solve it?

    Solution: Let's allow functions to return two kind of things: instead of having g : Real,Real -> Real (function from two reals into a real), let's allow g : Real,Real -> Real | Nothing (function from two reals into (real or nothing)).

  4. But functions should (to be simpler) return only one thing.

    Solution: let's create a new type of data to be returned, a "boxing type" that encloses maybe a real or be simply nothing. Hence, we can have g : Real,Real -> Maybe Real. OK, but ...

  5. What happens now to f(g(x,y))? f is not ready to consume a Maybe Real. And, we don't want to change every function we could connect with g to consume a Maybe Real.

    Solution: let's have a special function to "connect"/"compose"/"link" functions. That way, we can, behind the scenes, adapt the output of one function to feed the following one.

    In our case: g >>= f (connect/compose g to f). We want >>= to get g's output, inspect it and, in case it is Nothing just don't call f and return Nothing; or on the contrary, extract the boxed Real and feed f with it. (This algorithm is just the implementation of >>= for the Maybe type). Also note that >>= must be written only once per "boxing type" (different box, different adapting algorithm).

  6. Many other problems arise which can be solved using this same pattern: 1. Use a "box" to codify/store different meanings/values, and have functions like g that return those "boxed values". 2. Have a composer/linker g >>= f to help connecting g's output to f's input, so we don't have to change any f at all.

  7. Remarkable problems that can be solved using this technique are:

    • having a global state that every function in the sequence of functions ("the program") can share: solution StateMonad.

    • We don't like "impure functions": functions that yield different output for same input. Therefore, let's mark those functions, making them to return a tagged/boxed value: IO monad.

Total happiness!

Alexis King
  • 40,717
  • 14
  • 119
  • 194
cibercitizen1
  • 19,104
  • 14
  • 65
  • 90
  • 65
    @Carl Please write a better answer to enlighten us – XrXr Jan 25 '15 at 18:00
  • 15
    @Carl I think that it is clear in the answer that there are many many problems that benefit from this pattern (point 6) and that `IO` monad is just one more problem in the list `IO` (point 7). On the other hand `IO` only appears once and at the end, so, don't understand your "most of its time talking ... about IO". – cibercitizen1 Jan 25 '15 at 18:02
  • 1
    Does `>>=` have to be written once per (f,g,Monad) or (g,Monad)? – yurez Jan 26 '15 at 09:17
  • 1
    @yurez Only per monad. If you want a class to be a monad you have to define for it `>>=` and `return`. I.E `class Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b ` (See: http://learnyouahaskell.com/a-fistful-of-monads) – cibercitizen1 Jan 26 '15 at 09:23
  • 3
    Function composition doesn't determine which function is executed first (i.e. writing `\x y -> f (g x y)` doesn't mean that `g` will be executed before `f`). That is determined by the evaluation strategy. To prove this evaluate the following in GHCi, `let assert = const True . error` and `assert "Function composition doesn't determine evaluation order."` The result is `True`, not an error. In a strict language it would throw an error. However because Haskell is non-strict by default it doesn't do so. Instead it evaluates `const` first and doesn't evaluate `error` at all. – Aadit M Shah Jan 26 '15 at 10:11
  • @AaditMShah "The exception confirms the rule" – cibercitizen1 Jan 26 '15 at 10:47
  • 1
    @cibercitizen1 That's a weak excuse for when you don't know what the rule is :) – BartoszKP Jan 26 '15 at 11:24
  • 1
    @BartoszKP In `const True . error` Does `const True` depends on `error`? Great example to prove what? That if you compose functions in which don't depend the outer function on the inner function's output, the inner one doesn't need to be evaluated? Great ;-) – cibercitizen1 Jan 26 '15 at 11:49
  • 1
    @cibercitizen1 I didn't say anything about `const True .error`, nor about any dependencies nor about composing functions. I don't know whether you're right or wrong. I've only evaluated your previously expressed argument. – BartoszKP Jan 26 '15 at 11:52
  • @carl not just IO, but it just happens that Monads (already known to the math people) provides a way to address *several* of the "hard parts" of pure functional programming, and the State hard part pretty much solves everything else. So, one set of syntactic sugar gives a lot of benefits. It is, in essence, adding stateful things into Haskell in a palletable manner. – JDługosz Jan 26 '15 at 12:42
  • 5
    The great misconceptions about monads: monads about state; monads about exception handling; there is no way to implement IO in pure FPL without monads;monads are unambiguous (contrargument is `Either`). The most of answer is about "Why do we need functors?". – vlastachu Jan 27 '15 at 11:52
  • 4
    *"6. 2. Have a composer/linker `g >>= f` to help connecting `g`'s output to `f`'s input, so we don't have to change any `f` at all."* **this is not right** at all. Before, in `f(g(x,y))`, `f` could produce anything. It could be `f:: Real -> String`. With "monadic composition" **it must be changed** to produce `Maybe String`, or else the types won't fit. Moreover, **`>>=`** itself **doesn't fit**!! It's `>=>` that does this composition, not `>>=`. See the discussion with dfeuer under Carl's answer. – Will Ness Jan 29 '15 at 10:02
  • 3
    Your answer is right in the sense that monads IMO indeed are best described as being about the composition/ality of "functions" (Kleisli arrows really), but the precise details of what type goes where are what makes them "monads". you could wire the boxes in all kinds of manners (like Functor, etc.). This *specific* way of wiring them together is what defines "the monad". – Will Ness Jan 29 '15 at 10:09
  • For anyone learning Scala, the equivalent of the compose function (`>>=`) in Scala is usually the `map` method. For example, `Option.map` will apply the given function to the boxed in data if the type is `Some` and do nothing if the type if `None`. So instead of `g >>= f`, we'd use `g map f`. The `Try` monad works similarly, but `Failure` will store an exception (and calling map on a `Failure` returns the unmodified `Failure`). – Kat Jan 31 '15 at 20:12
229

The answer is, of course, "We don't". As with all abstractions, it isn't necessary.

Haskell does not need a monad abstraction. It isn't necessary for performing IO in a pure language. The IO type takes care of that just fine by itself. The existing monadic desugaring of do blocks could be replaced with desugaring to bindIO, returnIO, and failIO as defined in the GHC.Base module. (It's not a documented module on hackage, so I'll have to point at its source for documentation.) So no, there's no need for the monad abstraction.

So if it's not needed, why does it exist? Because it was found that many patterns of computation form monadic structures. Abstraction of a structure allows for writing code that works across all instances of that structure. To put it more concisely - code reuse.

In functional languages, the most powerful tool found for code reuse has been composition of functions. The good old (.) :: (b -> c) -> (a -> b) -> (a -> c) operator is exceedingly powerful. It makes it easy to write tiny functions and glue them together with minimal syntactic or semantic overhead.

But there are cases when the types don't work out quite right. What do you do when you have foo :: (b -> Maybe c) and bar :: (a -> Maybe b)? foo . bar doesn't typecheck, because b and Maybe b aren't the same type.

But... it's almost right. You just want a bit of leeway. You want to be able to treat Maybe b as if it were basically b. It's a poor idea to just flat-out treat them as the same type, though. That's more or less the same thing as null pointers, which Tony Hoare famously called the billion-dollar mistake. So if you can't treat them as the same type, maybe you can find a way to extend the composition mechanism (.) provides.

In that case, it's important to really examine the theory underlying (.). Fortunately, someone has already done this for us. It turns out that the combination of (.) and id form a mathematical construct known as a category. But there are other ways to form categories. A Kleisli category, for instance, allows the objects being composed to be augmented a bit. A Kleisli category for Maybe would consist of (.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c) and id :: a -> Maybe a. That is, the objects in the category augment the (->) with a Maybe, so (a -> b) becomes (a -> Maybe b).

And suddenly, we've extended the power of composition to things that the traditional (.) operation doesn't work on. This is a source of new abstraction power. Kleisli categories work with more types than just Maybe. They work with every type that can assemble a proper category, obeying the category laws.

  1. Left identity: id . f = f
  2. Right identity: f . id = f
  3. Associativity: f . (g . h) = (f . g) . h

As long as you can prove that your type obeys those three laws, you can turn it into a Kleisli category. And what's the big deal about that? Well, it turns out that monads are exactly the same thing as Kleisli categories. Monad's return is the same as Kleisli id. Monad's (>>=) isn't identical to Kleisli (.), but it turns out to be very easy to write each in terms of the other. And the category laws are the same as the monad laws, when you translate them across the difference between (>>=) and (.).

So why go through all this bother? Why have a Monad abstraction in the language? As I alluded to above, it enables code reuse. It even enables code reuse along two different dimensions.

The first dimension of code reuse comes directly from the presence of the abstraction. You can write code that works across all instances of the abstraction. There's the entire monad-loops package consisting of loops that work with any instance of Monad.

The second dimension is indirect, but it follows from the existence of composition. When composition is easy, it's natural to write code in small, reusable chunks. This is the same way having the (.) operator for functions encourages writing small, reusable functions.

So why does the abstraction exist? Because it's proven to be a tool that enables more composition in code, resulting in creating reusable code and encouraging the creation of more reusable code. Code reuse is one of the holy grails of programming. The monad abstraction exists because it moves us a little bit towards that holy grail.

cmaher
  • 4,454
  • 1
  • 18
  • 33
Carl
  • 24,118
  • 4
  • 57
  • 79
  • 2
    Can you explain the relationship between categories generally and Kleisli categories? The three laws you describe hold in any category. – dfeuer Jan 25 '15 at 20:58
  • 1
    @dfeuer Oh. To put it in code, `newtype Kleisli m a b = Kleisli (a -> m b)`. Kleisli categories are functions where the categorical return type (`b` in this case) is the argument to a type constructor `m`. Iff `Kleisli m` forms a category, `m` is a Monad. – Carl Jan 25 '15 at 23:43
  • 1
    What is a categorical return type exactly? `Kleisli m` seems to form a category whose objects are Haskell types and such that the arrows from `a` to `b` are the functions from `a` to `m b`, with `id = return` and `(.) = (<= – dfeuer Jan 26 '15 at 02:32
  • 1
    @dfeuer That's correct. The objects are all types, and the morphisms are between types `a` and `b`, but they're not simple functions. They're decorated with an extra `m` in the return value of the function. – Carl Jan 26 '15 at 04:34
  • 1
    Is the Category Theory terminology really needed? Maybe, Haskell would be easier if you turned the types into pictures where the type would be the DNA for how the pictures are drawn (dependent types though*), and then you use the picture to write your program with the names being small ruby characters above the icon. – aoeu256 Oct 19 '19 at 00:39
24

Benjamin Pierce said in TAPL

A type system can be regarded as calculating a kind of static approximation to the run-time behaviours of the terms in a program.

That's why a language equipped with a powerful type system is strictly more expressive, than a poorly typed language. You can think about monads in the same way.

As @Carl and sigfpe point, you can equip a datatype with all operations you want without resorting to monads, typeclasses or whatever other abstract stuff. However monads allow you not only to write reusable code, but also to abstract away all redundant detailes.

As an example, let's say we want to filter a list. The simplest way is to use the filter function: filter (> 3) [1..10], which equals [4,5,6,7,8,9,10].

A slightly more complicated version of filter, that also passes an accumulator from left to right, is

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

To get all i, such that i <= 10, sum [1..i] > 4, sum [1..i] < 25, we can write

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

which equals [3,4,5,6].

Or we can redefine the nub function, that removes duplicate elements from a list, in terms of filterAccum:

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4] equals [1,2,4,5,3,8,9]. A list is passed as an accumulator here. The code works, because it's possible to leave the list monad, so the whole computation stays pure (notElem doesn't use >>= actually, but it could). However it's not possible to safely leave the IO monad (i.e. you cannot execute an IO action and return a pure value — the value always will be wrapped in the IO monad). Another example is mutable arrays: after you have leaved the ST monad, where a mutable array live, you cannot update the array in constant time anymore. So we need a monadic filtering from the Control.Monad module:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterM executes a monadic action for all elements from a list, yielding elements, for which the monadic action returns True.

A filtering example with an array:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

prints [1,2,4,5,3,8,9] as expected.

And a version with the IO monad, which asks what elements to return:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

E.g.

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

And as a final illustration, filterAccum can be defined in terms of filterM:

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

with the StateT monad, that is used under the hood, being just an ordinary datatype.

This example illustrates, that monads not only allow you to abstract computational context and write clean reusable code (due to the composability of monads, as @Carl explains), but also to treat user-defined datatypes and built-in primitives uniformly.

user3237465
  • 12,116
  • 2
  • 15
  • 33
  • 1
    This answer explains, why we need the Monad typeclass. The best way to understand, why we need monads and not something else, is to read about difference between monads and applicative functors: [one](http://stackoverflow.com/questions/23342184/difference-between-monad-and-applicative-in-haskell), [two](http://stackoverflow.com/questions/17409260/what-advantage-does-monad-give-us-over-an-applicative). – user3237465 Feb 03 '15 at 05:51
22

I don't think IO should be seen as a particularly outstanding monad, but it's certainly one of the more astounding ones for beginners, so I'll use it for my explanation.

Naïvely building an IO system for Haskell

The simplest conceivable IO system for a purely-functional language (and in fact the one Haskell started out with) is this:

main₀ :: String -> String
main₀ _ = "Hello World"

With lazyness, that simple signature is enough to actually build interactive terminal programs – very limited, though. Most frustrating is that we can only output text. What if we added some more exciting output possibilities?

data Output = TxtOutput String
            | Beep Frequency

main₁ :: String -> [Output]
main₁ _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

cute, but of course a much more realistic “alterative output” would be writing to a file. But then you'd also want some way to read from files. Any chance?

Well, when we take our main₁ program and simply pipe a file to the process (using operating system facilities), we have essentially implemented file-reading. If we could trigger that file-reading from within the Haskell language...

readFile :: Filepath -> (String -> [Output]) -> [Output]

This would use an “interactive program” String->[Output], feed it a string obtained from a file, and yield a non-interactive program that simply executes the given one.

There's one problem here: we don't really have a notion of when the file is read. The [Output] list sure gives a nice order to the outputs, but we don't get an order for when the inputs will be done.

Solution: make input-events also items in the list of things to do.

data IO₀ = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main₂ :: String -> [IO₀]
main₂ _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

Ok, now you may spot an imbalance: you can read a file and make output dependent on it, but you can't use the file contents to decide to e.g. also read another file. Obvious solution: make the result of the input-events also something of type IO, not just Output. That sure includes simple text output, but also allows reading additional files etc..

data IO₁ = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main₃ :: String -> [IO₁]
main₃ _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

That would now actually allow you to express any file operation you might want in a program (though perhaps not with good performance), but it's somewhat overcomplicated:

  • main₃ yields a whole list of actions. Why don't we simply use the signature :: IO₁, which has this as a special case?

  • The lists don't really give a reliable overview of program flow anymore: most subsequent computations will only be “announced” as the result of some input operation. So we might as well ditch the list structure, and simply cons a “and then do” to each output operation.

data IO₂ = TxtOut String IO₂
         | TxtIn (String -> IO₂)
         | Terminate

main₄ :: IO₂
main₄ = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

Not too bad!

So what has all of this to do with monads?

In practice, you wouldn't want to use plain constructors to define all your programs. There would need to be a good couple of such fundamental constructors, yet for most higher-level stuff we would like to write a function with some nice high-level signature. It turns out most of these would look quite similar: accept some kind of meaningfully-typed value, and yield an IO action as the result.

getTime :: (UTCTime -> IO₂) -> IO₂
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO₂
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO₂

There's evidently a pattern here, and we'd better write it as

type IO₃ a = (a -> IO₂) -> IO₂    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO₃ UTCTime
randomRIO :: Random r => (r,r) -> IO₃ r
findFile :: RegEx -> IO₃ (Maybe FilePath)

Now that starts to look familiar, but we're still only dealing with thinly-disguised plain functions under the hood, and that's risky: each “value-action” has the responsibility of actually passing on the resulting action of any contained function (else the control flow of the entire program is easily disrupted by one ill-behaved action in the middle). We'd better make that requirement explicit. Well, it turns out those are the monad laws, though I'm not sure we can really formulate them without the standard bind/join operators.

At any rate, we've now reached a formulation of IO that has a proper monad instance:

data IO₄ a = TxtOut String (IO₄ a)
           | TxtIn (String -> IO₄ a)
           | TerminateWith a

txtOut :: String -> IO₄ ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO₄ String
txtIn = TxtIn $ TerminateWith

instance Functor IO₄ where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO₄ where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO₄ where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

Obviously this is not an efficient implementation of IO, but it's in principle usable.

leftaroundabout
  • 101,764
  • 3
  • 156
  • 291
  • 1
    @jdlugosz: [`IO3 a ≡ Cont IO2 a`](http://hackage.haskell.org/package/transformers-0.4.2.0/docs/Control-Monad-Trans-Cont.html#t:Cont). But I meant that comment more as a nod to those who already know the continuation monad, as it doesn't exactly have a reputation as being beginner-friendly. – leftaroundabout Jan 26 '15 at 14:04
6

Monads serve basically to compose functions together in a chain. Period.

Now the way they compose differs across the existing monads, thus resulting in different behaviors (e.g., to simulate mutable state in the state monad).

The confusion about monads is that being so general, i.e., a mechanism to compose functions, they can be used for many things, thus leading people to believe that monads are about state, about IO, etc, when they are only about "composing functions".

Now, one interesting thing about monads, is that the result of the composition is always of type "M a", that is, a value inside an envelope tagged with "M". This feature happens to be really nice to implement, for example, a clear separation between pure from impure code: declare all impure actions as functions of type "IO a" and provide no function, when defining the IO monad, to take out the "a" value from inside the "IO a". The result is that no function can be pure and at the same time take out a value from an "IO a", because there is no way to take such value while staying pure (the function must be inside the "IO" monad to use such value). (NOTE: well, nothing is perfect, so the "IO straitjacket" can be broken using "unsafePerformIO : IO a -> a" thus polluting what was supposed to be a pure function, but this should be used very sparingly and when you really know to be not introducing any impure code with side-effects.

mljrg
  • 3,545
  • 1
  • 27
  • 42
5

Monads are just a convenient framework for solving a class of recurring problems. First, monads must be functors (i.e. must support mapping without looking at the elements (or their type)), they must also bring a binding (or chaining) operation and a way to create a monadic value from an element type (return). Finally, bind and return must satisfy two equations (left and right identities), also called the monad laws. (Alternatively one could define monads to have a flattening operation instead of binding.)

The list monad is commonly used to deal with non-determinism. The bind operation selects one element of the list (intuitively all of them in parallel worlds), lets the programmer to do some computation with them, and then combines the results in all worlds to single list (by concatenating, or flattening, a nested list). Here is how one would define a permutation function in the monadic framework of Haskell:

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

Here is an example repl session:

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

It should be noted that the list monad is in no way a side effecting computation. A mathematical structure being a monad (i.e. conforming to the above mentioned interfaces and laws) does not imply side effects, though side-effecting phenomena often nicely fit into the monadic framework.

heisenbug
  • 374
  • 3
  • 7
4

You need monads if you have a type constructor and functions that returns values of that type family. Eventually, you would like to combine these kind of functions together. These are the three key elements to answer why.

Let me elaborate. You have Int, String and Real and functions of type Int -> String, String -> Real and so on. You can combine these functions easily, ending with Int -> Real. Life is good.

Then, one day, you need to create a new family of types. It could be because you need to consider the possibility of returning no value (Maybe), returning an error (Either), multiple results (List) and so on.

Notice that Maybe is a type constructor. It takes a type, like Int and returns a new type Maybe Int. First thing to remember, no type constructor, no monad.

Of course, you want to use your type constructor in your code, and soon you end with functions like Int -> Maybe String and String -> Maybe Float. Now, you can't easily combine your functions. Life is not good anymore.

And here's when monads come to the rescue. They allow you to combine that kind of functions again. You just need to change the composition . for >==.

jdinunzio
  • 1,005
  • 1
  • 7
  • 20
2

Why do we need monadic types?

Monadic types aren't always needed - from How to Declare an Imperative by Philip Wadler:

(* page 25 *)
val echoML    : unit -> unit
fun echoML () = let val c = getcML () in
                if c = #"\n" then
                  ()
                else
                  (putcML c; echoML ())
                end

where:

(* pages 25-26 *)
fun putcML c  = TextIO.output1(TextIO.stdOut,c);
fun getcML () = valOf(TextIO.input1(TextIO.stdIn));

Yes, alright - you're probably trying to learn Haskell, and that's why you eventually ended up here. As it happens, it was the quandary of I/O in nonstrict languages like Haskell that brought the monadic interface to such prominence - that's why I've chosen I/O for the running example.

Now, you can write echo in Haskell like this:

echoH :: IO ()
echoH =  do c <- getChar
            if c == '\n' then
              return ()
            else
              putChar c >> echoH

or this:

echoH' :: IO ()
echoH' =  getChar   >>= \c ->
          if c == '\n' then return () else
          putChar c >> echoH'

but you cannot write this:

errcho    :: () -> ()
errcho () =  let c = getc () in
             if c == '\n' then
               ()
             else
               putc c ; errcho ()

 -- fake primitives!
(;)  :: a -> b -> b
putc :: Char -> ()
getc :: ()   -> Char

That ain't legit Haskell...but this almost is:

echo   :: OI -> ()
echo u =  let !u1:u2:u3:_ = parts u in
          let !c          = getchar u1 in
          if c == '\n' then () else putchar c u2 `seq` echo u3

where:

data OI             -- abstract
parts :: OI -> [OI] -- primitive

 -- I'll leave these definitions to you ;-)
putchar :: Char -> OI -> ()
getchar :: OI -> Char
  • Bang-patterns are an extension of Haskell 2010;

  • Prelude.seq isn't actually sequential - you would need an alternate definition of seq e.g:

       -- for GHC 8.6.5
      {-# LANGUAGE CPP #-}
      #define during seq
      import qualified Prelude(during)
    
      {-# NOINLINE seq #-}
      infixr  0 `seq`
      seq     :: a -> b -> b
      seq x y = Prelude.during x (case x of _ -> y)
    

    or:

       -- for GHC 8.6.5
      {-# LANGUAGE CPP #-}
      #define during seq
      import qualified Prelude(during)
      import GHC.Base(lazy)
    
      infixr 0 `seq`
      seq     :: a -> b -> b
      seq x y = Prelude.during x (lazy y)
    

    (Yes - more extensions are being used, but they stay with each definition.)

It's clunkier, but this is regular Haskell:

echo   :: OI -> ()
echo u =  case parts u of
            u1:u2:u3:_ -> case getchar u1 of
                            c -> if c == '\n' then () else
                                 case putchar c u2 of () -> echo u3

Yes, that's it: no passing of results to continuations, no bundling of results with some abstract state - just a plain ordinary result. All you need to do is provide an all-new OI value - what a novel concept!

It almost seems too good to be true...what about referential transparency: is it preserved?

Well, the way we're using these OI values is similar to the use of timestamps as described by F. Warren Burton in Nondeterminism with Referential Transparency in Functional Programming Languages - the main difference is that timestamps must first be retrieved from trees, whereas OI values can be used directly.

Burton's timestamps maintain referential transparency by ensuring:

  • the effects involved in determining a timestamp only occur once - upon its initial use;

  • once determined, a timestamp remains constant - it doesn't change, even if it's reused.

If OI values also work in this manner, then referential transparency is preserved.

Yes, it's a bit arcane, but together with a suitable definition of seq, parts and those curious OI values can allow you to do neat stuff like this:

runDialogue :: Dialogue -> OI -> ()    
runDialogue d =
    \u -> foldr seq () (yet (\l -> zipWith respond (d l) (parts u)))

respond :: Request -> OI -> Response
respond Getq     = getchar `bind` (unit . Getp)
respond (Putq c) = putchar c `bind` \_ -> unit Putp

where:

 -- types from page 14 of Wadler's paper
type Dialogue = [Response] -> [Request]

data Request  = Getq | Putq Char
data Response = Getp Char | Putp

yet      :: (a -> a) -> a
yet f    =  f (yet f)

unit     :: a -> (OI -> a)
unit x   =  \u -> part u `seq` x

bind     :: (OI -> a) -> (a -> (OI -> b)) -> (OI -> b)
bind m k =  \u -> case part u of (u1, u2) -> (\x -> x `seq` k x u2) (m u1)

part     :: OI -> (OI, OI)
part u   =  case parts u of u1:u2:_ -> (u1, u2)

It isn't working? Give this a try:

yet      :: (a -> a) -> a
yet f    =  y where y = f y

Yes, continually typing out OI -> would be annoying, and if this approach to I/O is going to work, it has to work everywhere. The simplest solution is:

type IO a = OI -> a

to avoid the hassle of wrapping and unwrapping involved with using constructors. The change of type also provides main with an alternate type signature:

main :: OI -> ()

To conclude - for Haskell, monadic types are a convenience

echo' :: OI -> ()
echo' =  getchar   `bind` \c ->
         if c == '\n' then unit () else
         putchar c `bind` \_ -> echo'

rather than being an absolute necessity.

atravers
  • 243
  • 5