28

A monad is a mathematical structure which is heavily used in (pure) functional programming, basically Haskell. However, there are many other mathematical structures available, like for example applicative functors, strong monads, or monoids. Some have more specific, some are more generic. Yet, monads are much more popular. Why is that?

One explanation I came up with, is that they are a sweet spot between genericity and specificity. This means monads capture enough assumptions about the data to apply the algorithms we typically use and the data we usually have fulfills the monadic laws.

Another explanation could be that Haskell provides syntax for monads (do-notation), but not for other structures, which means Haskell programmers (and thus functional programming researchers) are intuitively drawn towards monads, where a more generic or specific (efficient) function would work as well.

Community
  • 1
  • 1
qznc
  • 1,113
  • 2
  • 12
  • 25
  • 11
    Eh, I'm not sure about your premise: most of the more advanced Haskellers I know favor Applicatives to some degree. I certainly do. People also use monoids all the time, just for completely unrelated tasks. Categories also come up by themselves once in a while. Monads just get a lot of press in non-Haskell-using circuits, for whatever reason. – Tikhon Jelvis May 08 '13 at 15:52
  • 1
    @TikhonJelvis: I don't see that "most of the more advanced Haskellers I know favor Applicatives to some degree." The things I see getting the most attention are not particularly applicative-related. As an example, to use [something I've been working on](http://hackage.haskell.org/package/free-operational), I judge that free monads still command more interest than free applicatives. Interest on free applicatives has been on the rise lately, though. – Luis Casillas May 08 '13 at 17:43
  • 6
    As far as I recall, monads were popularised by Wadler, and at the time the idea of doing IO without tedious CPS and parsing without explicit state passing were huge selling points; it was a hugely exciting time. A.F.A.I.R., Haskell didn't do constructor classes, but Gofer (father of Hugs) did. Wadler proposed overloading list comprehension for monads, so the do notation came later. Once IO was monadic, monads became a big deal for beginners, cementing them as a major thing to grok. Applicatives are much nicer when you can, and Arrows more general, but they came later, and IO sells monads hard. – AndrewC May 09 '13 at 01:34

6 Answers6

29

I suspect that the disproportionately large attention given to this one particular type class (Monad) over the many others is mainly a historical fluke. People often associate IO with Monad, although the two are independently useful ideas (as are list reversal and bananas). Because IO is magical (having an implementation but no denotation) and Monad is often associated with IO, it's easy to fall into magical thinking about Monad.

(Aside: it's questionable whether IO even is a monad. Do the monad laws hold? What do the laws even mean for IO, i.e., what does equality mean? Note the problematic association with the state monad.)

Community
  • 1
  • 1
Conal
  • 18,517
  • 2
  • 37
  • 40
  • Heh, the bananas link is very amusing. What about the coroutine-based interpretation Edward Kmett mentions in your second link? – Tikhon Jelvis May 08 '13 at 15:56
  • 6
    The phrase "is magical" to mean "having an implementation but no denotation" is going in my toolbox. – J. Abrahamson May 08 '13 at 17:30
  • I've been curious about this in category theory. After I saw how the category of monoids was isomorphic to the category of algebras for the list monad (Eugenia Chang), I began to wonder if monads capture a very general and ubiquitous structure in mathematical theories? It strikes me that they may be much, much more than the way we use them in Haskell. I haven't put it all together yet, though. – luqui May 08 '13 at 19:42
  • 1
    I coulda sworn that we _did_ write list reversal with bananas! At least I don't recall needing lenses, envelopes, or barbed wire... – sclv May 08 '13 at 20:00
  • 3
    luqui: of course they do! take a look at lawvere's classic stuff or wells and barr's toposes, triples, and theories. – sclv May 08 '13 at 20:01
  • I'm curious in what sense `IO` could be said not to satisfy the monad laws. It's not equality in the `Eq` type class sense that's required in the law `return a >>= f ≡ f a`, but *equivalence*. Plenty of non-`IO` monads are types that can't be members of `Eq` (i.e. functions), but it seems wrong to doubt that they are monads because we can't say what equality is. Indeed any abstract monadic type has no equality. Instead we simply require that the program has identical observable behaviour whenever we replace `f a` with `return a >>= f` or vice versa. – Ben May 09 '13 at 11:11
  • 5
    Thanks, Ben. I have denotational equality in mind. (As an aside, when `Eq` instances do happen to exist, I always want their `(==)` to correspond to denotational equality.) Note that I did not suggest that `IO` fails to satisfy the monad laws (or indeed *any* equational laws), but rather that we need to precisely define equality (preferably denotationally, given our chosen programming paradigm) in order to have a meaningful question. – Conal May 09 '13 at 20:42
  • 1
    For more on this denotational perspective, see Peter Landin's seminal "The Next 700 Programming languages", especially sections 8 & 9, in which Landin proposes replacing fuzzy terms like "functional" and "declarative" with the substantive "denotative", which he also refers to as "genuinely functional" (rather than "merely masquerading"). You can find a [paper link and some exerpts in a blog comment](http://conal.net/blog/posts/is-haskell-a-purely-functional-language#comment-35882) I wrote a while back. – Conal May 09 '13 at 20:52
  • 1
    @luqui, sclv NB: I would definitely check out Lawvere's stuff /first/. TTT is one of the most difficult texts out there on this stuff -- still largely impenetrable for me at least. – Darius Jahandarie May 09 '13 at 21:04
  • 1
    @Conal `Eq` cannot really correspond to denotational equality, since we have bottom. But it can approximate it. – augustss May 09 '13 at 21:47
  • 1
    @augustss Good point. Monotonicity places a very strong constraint on what `(==)` can yield when ⊥ is involved. I'm happy with saying that `(==)` must approximate denotational equality in the sense that `μ (a == b) ⊑ (μ a ≡ μ b)`, where the (left) `(==)` is `Eq` equality and the (right) `(≡)` is mathematical/denotational equality. And we want the approximation to be as close (information-rich) as we can. – Conal May 09 '13 at 22:00
  • djahandarie: oh i can't understand the 'topos' portion nearly at all, but the extent i do understand of the other two ts answers luqui's comment fairly directly. One day... :-) – sclv May 11 '13 at 02:20
  • Regarding the abstract `IO` type *"having an implementation but no denotation"*: where is the denotation for the abstract type `(->)` in regular Haskell? – atravers Sep 14 '21 at 01:18
  • For all Haskell types `A` and `B` that have denotations (unlike `IO t`), the type Haskell `A -> B` denotes mathematical functions from the denotation of `A` to the denotation of `B`. More specifically, types in Haskell denote complete partial orders (CPOs), and functions are continuous (monotonic and limit-preserving). It is exactly this denotational nature of "regular Haskell" (the denotative subset) that allows relatively simple reasoning on mathematical meanings to transfer to correct reasoning on expressions, making rigor practical in regular Haskell. – Conal Oct 05 '21 at 00:30
  • Would [Wadler's](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.3579&rep=rep1&type=pdf) denotation of `IO t` (he restricts I/O to simple teletype interactions for brevity) be a useful basis for investigating how to expand that denotation? – atravers Oct 09 '21 at 11:27
  • No, it wouldn't. As Simon Peyton Jones said in "Wearing the hair shirt---A retrospective on Haskell", "The IO monad has become Haskell's sin-bin. Whenever we don't understand something, we toss it in the IO monad." He explicitly abandons the attempt to find a denotation at the end of section 3.1. Is it likely that someone can then come along later and give a compelling and denotation for all that's been added and all that possibly could be added to `IO` via the foreign function interface (*every* kind of effectful computation)? – Conal Oct 10 '21 at 22:49
17

If a type m :: * -> * has a Monad instance, you get Turing-complete composition of functions with type a -> m b. This is a fantastically useful property. You get the ability to abstract various Turing-complete control flows away from specific meanings. It's a minimal composition pattern that supports abstracting any control flow for working with types that support it.

Compare this to Applicative, for instance. There, you get only composition patterns with computational power equivalent to a push-down automaton. Of course, it's true that more types support composition with more limited power. And it's true that when you limit the power available, you can do additional optimizations. These two reasons are why the Applicative class exists and is useful. But things that can be instances of Monad usually are, so that users of the type can perform the most general operations possible with the type.

Edit: By popular demand, here are some functions using the Monad class:

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y

whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)

(*&&) :: Monad m => m Bool -> m Bool -> m Bool
x *&& y = ifM x y (return False)

(*||) :: Monad m => m Bool -> m Bool -> m Bool
x *|| y = ifM x (return True) y

notM :: Monad m => m Bool -> m Bool
notM x = x >>= return . not

Combining those with do syntax (or the raw >>= operator) gives you name binding, indefinite looping, and complete boolean logic. That's a well-known set of primitives sufficient to give Turing completeness. Note how all the functions have been lifted to work on monadic values, rather than simple values. All monadic effects are bound only when necessary - only the effects from the chosen branch of ifM are bound into its final value. Both *&& and *|| ignore their second argument when possible. And so on..

Now, those type signatures may not involve functions for every monadic operand, but that's just a cognitive simplification. There would be no semantic difference, ignoring bottoms, if all the non-function arguments and results were changed to () -> m a. It's just friendlier to users to optimize that cognitive overhead out.

Now, let's look at what happens to those functions with the Applicative interface.

ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y

Well, uh. It got the same type signature. But there's a really big problem here already. The effects of both x and y are bound into the composed structure, regardless of which one's value is selected.

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <$> step x) (pure x)

Well, ok, that seems like it'd be ok, except for the fact that it's an infinite loop because ifA will always execute both branches... Except it's not even that close. pure x has the type f a. whileA p step <$> step x has the type f (f a). This isn't even an infinite loop. It's a compile error. Let's try again..

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)

Well shoot. Don't even get that far. whileA p step has the type a -> f a. If you try to use it as the first argument to <*>, it grabs the Applicative instance for the top type constructor, which is (->), not f. Yeah, this isn't gonna work either.

In fact, the only function from my Monad examples that would work with the Applicative interface is notM. That particular function works just fine with only a Functor interface, in fact. The rest? They fail.

Of course it's to be expected that you can write code using the Monad interface that you can't with the Applicative interface. It is strictly more powerful, after all. But what's interesting is what you lose. You lose the ability to compose functions that change what effects they have based on their input. That is, you lose the ability to write certain control-flow patterns that compose functions with types a -> f b.

Turing-complete composition is exactly what makes the Monad interface interesting. If it didn't allow Turing-complete composition, it would be impossible for you, the programmer, to compose together IO actions in any particular control flow that wasn't nicely prepackaged for you. It was the fact that you can use the Monad primitives to express any control flow that made the IO type a feasible way to manage the IO problem in Haskell.

Many more types than just IO have semantically valid Monad interfaces. And it happens that Haskell has the language facilities to abstract over the entire interface. Due to those factors, Monad is a valuable class to provide instances for, when possible. Doing so gets you access to all the existing abstract functionality provided for working with monadic types, regardless of what the concrete type is.

So if Haskell programmers seem to always care about Monad instances for a type, it's because it's the most generically-useful instance that can be provided.

Carl
  • 26,500
  • 4
  • 65
  • 86
  • 3
    What on earth does Turing-completeness have to do with it? (Note that languages that are not Turing-complete are perfectly capable of using monads, cf. Agda) – Ben Millwood May 16 '13 at 09:49
  • 1
    @BenMillwood Agda allows you to use many structures that are Turing-complete in general, so long as you prove that your specific use of them will terminate - like non-structural recursion. The point is that the monad interface allows you to describe Turing-complete patterns, not that it requires you to use them. This is because `=<<` allows you let the recursion structure used by its first operand depend on the value of the second operand. The next-closest composition operator, `<*>` from `Applicative`, requires that the recursion structure not depend on the value of the second operand. – Carl May 16 '13 at 16:11
  • 3
    I still think that the difference between `Applicative` and `Monad` isn't anything to do with Turing-completeness. I don't see how the concepts even relate – Turing-completeness means, roughly "is able to simulate a Turing machine, and can be simulated by one". Naturally the latter condition holds for everything under discussion here. Can the monadic composition of functions `a -> m b` simulate a Turing machine? How would it even attempt to do so? – Ben Millwood May 17 '13 at 13:36
  • 1
    Turing-completeness is also equivalent to "Can parse context-sensitive grammars". Applicative parsers can parse context-free grammars at most. Monadic parsers can parse context-sensitve grammars. This is the fundamental difference in power between the interfaces. – Carl May 17 '13 at 15:32
  • I'm still not convinced. First of all, context-sensitive grammars can be parsed by *linear-bounded* NDTMs, which are strictly less capable than general Turing machines. In particular (unless I've misunderstood) the halting problem for linear-bounded TMs is decidable, since they have only finitely many possible states. So, no, parsing CSGs is not equivalent to Turing-completeness. Moreover, I'm still unconvinced by the general thrust of your argument, because Haskell is already Turing-complete without any type classes or higher-order types or indeed data types at all. – Ben Millwood May 17 '13 at 21:59
  • @BenMillwood Well, then ignore the grammar case. It's clearly not a complete story. The giant edit *is* a complete illustration. You can't just be "unconvinced" by it unless you can actually say where it's wrong, without ignoring any parts of it. For instance, Turing-complete composition of functions of the form `a -> b` is completely irrelevant to Turing-complete composition of functions of the form `a -> m b`. You can't just ignore that detail. It matters. – Carl May 17 '13 at 22:33
  • 3
    Here's what I'm missing: Turing-completeness is a property of models of computation – processes that take some input and produce some output. I can't see how composition of a specific variety of functions constitutes a model of computation, except in the sense that Haskell itself is one. Where does the input go? Where does the output come from? – Ben Millwood May 18 '13 at 10:00
  • @BenMillwood Ah. That's actually a good objection. I think I can answer it, but I'll need to be precise about it. I'll come back to this when I have a precise answer. – Carl May 18 '13 at 18:22
  • @Carl : regarding your comment above : `I'll come back to this when I have a precise answer`, did you arrive at your clarification in the end? Thanks. – artella Jun 28 '14 at 20:25
  • @artella I would love to see the proper final answer to the objection as well. On a purely instinctive level, I can raise two tentative counters: (i) Wasn't that famous paper by Eugenio Moggi all about modelling computation with monads? (ii) Isn't the point of all of the tinkering with category theory we see in Haskell to work with notions of composition different than the one associated with vanilla Haskell functions (that is, the **Hask** category)? – duplode Jul 23 '15 at 19:24
  • @Carl can you show me something useful you can do with logic inside monads? – Chet Mar 24 '16 at 04:37
12

First, I think that it is not quite true that monads are much more popular than anything else; both Functor and Monoid have many instances that are not monads. But they are both very specific; Functor provides mapping, Monoid concatenation. Applicative is the one class that I can think of that is probably underused given its considerable power, due largely to its being a relatively recent addition to the language.

But yes, monads are extremely popular. Part of that is the do notation; a lot of Monoids provide Monad instances that merely append values to a running accumulator (essentially an implicit writer). The blaze-html library is a good example. The reason, I think, is the power of the type signature (>>=) :: Monad m => m a -> (a -> m b) -> m b. While fmap and mappend are useful, what they can do is fairly narrowly constrained. bind, however, can express a wide variety of things. It is, of course, canonized in the IO monad, perhaps the best pure functional approach to IO before streams and FRP (and still useful beside them for simple tasks and defining components). But it also provides implicit state (Reader/Writer/ST), which can avoid some very tedious variable passing. The various state monads, especially, are important because they provide a guarantee that state is single threaded, allowing mutable structures in pure (non-IO) code before fusion. But bind has some more exotic uses, such as flattening nested data structures (the List and Set monads), both of which are quite useful in their place (and I usually see them used desugared, calling liftM or (>>=) explicitly, so it is not a matter of do notation). So while Functor and Monoid (and the somewhat rarer Foldable, Alternative, Traversable, and others) provide a standardized interface to a fairly straightforward function, Monad's bind is considerably more flexibility.

In short, I think that all your reasons have some role; the popularity of monads is due to a combination of historical accident (do notation and the late definition of Applicative) and their combination of power and generality (relative to functors, monoids, and the like) and understandability (relative to arrows).

isturdy
  • 1,231
  • 8
  • 12
7

Well, first let me explain what the role of monads is: Monads are very powerful, but in a certain sense: You can pretty much express anything using a monad. Haskell as a language doesn't have things like action loops, exceptions, mutation, goto, etc. Monads can be expressed within the language (so they are not special) and make all of these reachable.

There is a positive and a negative side to this: It's positive that you can express all those control structures you know from imperative programming and a whole bunch of them you don't. I have just recently developed a monad that lets you reenter a computation somewhere in the middle with a slightly changed context. That way you can run a computation, and if it fails, you just try again with slightly adjusted values. Furthermore monadic actions are first class, and that's how you build things like loops or exception handling. While while is primitive in C in Haskell it's actually just a regular function.

The negative side is that monads give you pretty much no guarantees whatsoever. They are so powerful that you are allowed to do whatever you want, to put it simply. In other words just like you know from imperative languages it can be hard to reason about code by just looking at it.

The more general abstractions are more general in the sense that they allow some concepts to be expressed which you can't express as monads. But that's only part of the story. Even for monads you can use a style known as applicative style, in which you use the applicative interface to compose your program from small isolated parts. The benefit of this is that you can reason about code by just looking at it and you can develop components without having to pay attention to the rest of your system.

ertes
  • 4,430
  • 1
  • 18
  • 23
  • 2
    There are various things, which can be expressed with Functor or Applicative, but not with Monad. Why are Haskell programmer still much more enamored with Monads? http://stackoverflow.com/a/7220548/2361979 – qznc May 08 '13 at 11:55
  • IMHO, because the community first learned how to tackle the more general problem, and is still learning about how to use restricted subcases of it. In recent times `Applicative` has been getting traction and up-ending things in this front, but we still have a ways to go. – Luis Casillas May 08 '13 at 17:31
  • 1
    @qznc - There is nothing that can be expressed by the `Functor` or `Applicative` interfaces that cannot be expressed by the `Monad` interface. `Monad` is strictly more powerful. Your link is about implementing the interfaces, which is the other direction. Of course strictly less powerful interfaces have more (or equal, but not in this case) possible implementations. – Carl May 17 '13 at 15:39
  • Using for loop as a example with `[]`, i found it a bit crazy that `do` allows you bind another context, which means you can have higher dimension data bind to lower dimension variable, which means you can express things without writing for loop. – windmaomao Nov 26 '20 at 21:15
2

What is so special about monads?

The monadic interface's main claim to fame in Haskell is its role in the replacement of the original and unwieldy dialogue-based I/O mechanism.

As for their status in a formal investigative context...it is merely an iteration of a seemingly-cyclic endeavour which is now (2021 Oct) approximately one half-century old:

During the 1960s, several researchers began work on proving things about programs. Efforts were made to prove that:

  • A program was correct.

  • Two programs with different code computed the same answers when given the same inputs.

  • One program was faster than another.

  • A given program would always terminate.

While these are abstract goals, they are all, really, the same as the practical goal of "getting the program debugged".

Several difficult problems emerged from this work. One was the problem of specification: before one can prove that a program is correct, one must specify the meaning of "correct", formally and unambiguously. Formal systems for specifying the meaning of a program were developed, and they looked suspiciously like programming languages.

The Anatomy of Programming Languages, Alice E. Fischer and Frances S. Grodzinsky.

(emphasis by me.)

...back when "programming languages" - apart from an intrepid few - were most definitely imperative.

Anyone for elevating this mystery to the rank of Millenium problem? Solving it would definitely advance the science of computing and the engineering of software, one way or the other...

atravers
  • 455
  • 4
  • 8
0

Monads are special because of do notation, which lets you write imperative programs in a functional language. Monad is the abstraction that allows you to splice together imperative programs from smaller, reusable components (which are themselves imperative programs). Monad transformers are special because they represent enhancing an imperative language with new features.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198