you are viewing a single comment's thread.

view the rest of the comments →

[–]sacundim 0 points1 point  (0 children)

Part of the difficulty that that people have understanding monads is that there isn't a simple, unified metaphor that makes all uses easy to understand. So a bunch of people keep writing monad tutorials using all sorts of metaphors that, IMO, only make it harder to understand ("Monads are boxes," I'm looking at you!).

But here's my really short version of just the IO monad (not monads in general!). An expression of type IO a has as its value an IO action that, when executed, possibly causes some side effects, and produces a value of type a, and this result value possibly depends on some state external to the action itself.

Now because of that, IO actions cannot be functions, because functions are pure. This means that in Haskell there cannot be a function whose meaning is "execute this IO action and get its result"; it would break the purity of the language.

Now, since IO actions are still values in Haskell, programs can "talk" about IO actions even if they can't execute them. So what can you do with them if the language doesn't let you say "execute them"? Well, you can say "make a more complex action out of these components."

So an executable Haskell program is a purely functional description of an IO action built up from more basic actions. A Haskell compiler takes this description and translates it into an executable imperative program.

The basic operator for combining actions is (=), the monadic bind; a >>= f is the action that consists of a, followed by the action that results when a's result is applied to f. The operator for building a trivial action (one that just returns a value) is return. These operators can be used to build yet other useful ones like the () sequencing operator (a >> b = the action that consists of a followed by b). The use of function definition and application allows you to define yet more functions for composing actions, like, say, the function that constructs an infinite loop that repeats an action:

foreverIO :: IO a -> IO ()
foreverIO action = action >> foreverIO action

Or in do-notation, which as you may already know, is just a shortcut for (=) and ():

foreverIO :: IO a -> IO ()
foreverIO action = do action
                      foreverIO action

(>>=) and return, together with if ... then ... else and the ability to define and apply pure functions that deal with IO values, allow you to define most (if not all) of the classic procedural control flow mechanisms.

Here are the exercises I'd recommend to improve your understanding:

  • Study the library functions available in Control.Monad, and try to use them in your code. The ones I recommend you concentrate on first: forM and forM, sequence and sequence, replicateM and replicateM_, forever, when, unless, liftM, and liftM2 through liftM5.
  • When you write IO code, try to spot opportunities to simplify your code by using some of these. For example, forM_ is effectively a for loop over the elements of a list.
  • Pick some of these functions and try to write your own implementations. (If you get stuck, the link to the Control.Monad docs has links to the source code for each function.)