12

I am familiar with monads in category theory (they are a very easy concept, in fact), yet >>= function in Haskell completely puzzles me. Ok, so applying bind to a value of M a and a function a -> M u is the same as first applying the monad to this function, then evaluating it at the specified value and multiplying the result: a >>= f is the same as join $ (fmap f) $ a. But how is this a natural description of computation? Is there some useful way of looking at it that would help me understand it?

Is there some nice article somewhere that is not geared towards someone fresh from the C++ jungle?

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
Alexei Averchenko
  • 1,706
  • 1
  • 16
  • 29
  • 1
    I think `>>=` represents more of the "plumbing" aspect of a monad, which is practically important, but theoretically quite uninteresting. I found that it's often easier to implement `join` than `>=` for a monad, and I guess it would have been cleverer to use `join` to define a monad. – Landei Jan 31 '12 at 11:51
  • 1
    Possibly relevant: http://stackoverflow.com/questions/8221395/what-is-a-monad-in-fp-in-categorical-terms – Louis Wasserman Jan 31 '12 at 23:26
  • 1
    @Landei you can always define it as `a >>= f = join (fmap f a) where join = ...;`, assuming an already-existing `Functor` instance. – Dan Burton Feb 02 '12 at 04:43
  • Yes, I know, but it would habe been nicer to have already definitions for both methods (in terms of each other), and leave the choice which one to implement to the user (like for `Eq.(==)` and `Eq.(/=)`). – Landei Feb 02 '12 at 13:26

4 Answers4

9

Consider the monadic function composition operator <=<. This is analogous to . except it works on monadic functions. It can be defined simply in terms of >>=, so learning about one will educate us about the other.

(<=<) :: (a -> m b) -> (b -> m c) -> a -> m c
(f <=< g) x =  g x >>= f

(.) :: (a -> b) -> (b -> c) -> a -> c
(f . g) x = g x |> f
  where z |> h = h z

In the case of ., g is "performed" first, and then f is performed on the output of g. In the case of <=<, g and its effects are "performed" first, and then f and its effects are performed. It's a bit of a misnomer to say that one happens "before" the other, actually, since not all monads work that way.

Perhaps it is more accurate to say that f can take advantage of additional contextual information provided by g. But that's not entirely correct, since g could potentially take away contextual information. If you want to 100% correctly describe monads, you really have to walk on eggshells.

But in almost all nontrivial cases, f <=< g means that the effects (as well as the result) of the monadic function g will subsequently influence the behavior of the monadic function f.


To address questions about v >>= f = join (fmap f v)

Consider f :: a -> m b and v :: m a. What does it mean to fmap f v? Well fmap :: (c -> d) -> m c -> m d, and in this case c = a and d = m b, so fmap f :: m a -> m (m b). Now, of course, we can apply v :: m a to this function, resulting in m (m b). but what exactly does that result type m (m b) mean?

The inner m represents the context from produced from f. The outer m represents the context originating from v (n.b. fmap should not disturb this original context).

And then you join that m (m b), smashing those two contexts together into m a. This is the heart of the definition of Monad: you must provide a way to smash contexts together. You can inspect the implementation details of various Monad instances to try and understand how they "smash" contexts together. The takeaway here, though, is that the "inner context" is unobservable until you merge it with the "outer context". If you use v >>= f, then there is no actual notion of the function f receiving a pure value a and producing a simple monadic result m b. Instead, we understand that f acts inside the original context of v.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
  • How can `f` take advantage of additional contextual information if its domain is not monadic? – Alexei Averchenko Feb 01 '12 at 14:53
  • @AlexeiAverchenko because its range *is* monadic. That is precisely the magic of monads: even though a monadic function's input is *not* monadic, the output *is*. Therefore, `>>=` is a way to convey the context of a monadic value `m a` in order to affect the result of a monadic function `a -> m b`. Note that `m` must be *the same* for both of these. It makes no sense to do, say, `[1,2,3] >>= print`, because [] and IO are not the same. – Dan Burton Feb 02 '12 at 01:14
  • But the function `a -> m b` has no way to access the additional data of `m a`, that data are simply multiplied by `join :: m m b -> m b`. – Alexei Averchenko Feb 02 '12 at 03:49
  • @AlexeiAverchenko I've expanded my answer with some more thoughts on the matter. – Dan Burton Feb 02 '12 at 04:48
  • This talk of context seems more comonadic rather than monadic to me :) I understand that we want to build some sequence of things to do, and with list being the free monoid it's likely that a monoid generated by some atomic actions (and understood as a 'list with congruences') is the most general way to represent that, but why do we want to categorify this, is it to recover some flow control? – Alexei Averchenko Feb 02 '12 at 06:12
  • 1
    @AlexeiAverchenko I'm no category theorist, but I've heard it explained as "Monads are for smashing contexts together, Comonads are for pulling contexts apart". If you're looking for justification of using Monads to model IO, you may be interested in [Philip Wadler's monad papers](http://homepages.inf.ed.ac.uk/wadler/topics/monads.html), including but not limited to *How to declare an imperative* and *Imperative functional programming*. But `Monad` has (almost by accident, it seems) proven to be a useful abstraction for more than just IO. – Dan Burton Feb 02 '12 at 19:34
6

Hmm. I think a good way to think of it is that >>= lets you compose computations; the computations themselves are in the form a -> m b. So m b just represents the result of a computation.

So a computation just takes some value and produces some result. A good example here is the list type: a -> [b] represents a non-deterministic computation. It takes one input but can produce multiple results. By itself, the a -> [b] as a computation makes sense. But how would you combine these? The natural answer is that you would perform each consecutive "computation" on all of the previous results. And this is exactly what >>= does for lists.

One thing that really helped me see the practical value of this was thinking about DFAs and NFAs. You can imagine trivially writing a DFA in Haskell something like this:

data State = S1 | S2 | S3 | S4 | Q
data Input = A | B
transition :: State -> Input -> State
transition S1 A = S2
transition S1 B = S3
-- and so on...

Then we could just fold over input:

 foldl transition S1 [A, A, B, B]

Now, how would we take this code and generalize it to NFAs? Well, the transition "function" for an NFA can be thought of as a non-deterministic computation. So we define something like:

transition S1 A = [S1, S2]
transition S1 B = []

But now we'd have to do some weird gymnastics to use foldl! Happily, we can just foldM instead. So here the "computation" modeled by the monad is the non-deterministic transition function.

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
3

Perhaps =<< is easier to understand from a computation point of view (it's just flip (>>=)). It has typing (=<<) :: (Monad m) => (a -> m b) -> m a -> m b, and corresponds to the type of function application, cf ($) :: (a -> b) -> a -> b. So >>= is just flipped function application on the monadic level.

Also, (>>=) is used in desugaring do notation, and do syntactically corresponds very much to imperative code (in a suitable monad).

augustss
  • 22,884
  • 5
  • 56
  • 93
  • Or you can think of `=<<` as a lifting function. It lifts a monadic function `a -> m b` to also accept monadic input `m a -> m b`. – Dan Burton Jan 31 '12 at 08:39
3

Here's a rough idea of how it works out as a model of computation: A type constructor M with an instance of Monad represents a parametric data structure, and the non-parametric parts of that structure can contain other information. The return and join correspond to some sort of monoid for those parts of the structure. A function a -> M b introduces information in that structure, based on an input of type a. So by lifting a function a -> M b to M a -> M b we're using the parametric information of M to create some non-parametric information, then combining that with the information already present in a value of type M a.

The asymmetric nature of the type a -> M b gives an inherent direction to the flow of non-parametric information, while the associativity requirement means that the overall order is the only thing that matters.

The end result is augmenting functions with some sort of context that has a built-in notion of cause-and-effect.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302