Showing posts with label monads. Show all posts
Showing posts with label monads. Show all posts

Saturday, April 28, 2007

I understand comonads

Upon discovering the incredible coolness of monads, I decided that the closely related concept of comonads had to be at least as interesting. Unfortunately, far fewer people seem to be interested in this little underestimated cousin, at least not enough for anyone to post a spacesuit metaphor for them. But as always, when proper documentation is lacking, it suffices to ask the source.

haskell class Monad m where haskell (>>=) :: m a -> (a -> m b) -> m b haskell return :: a -> m a haskell haskell class Comonad w where haskell (=>>) :: w a -> (w a -> b) -> w b haskell extract :: w a -> a

Okay, so this time the source didn't help much, since there are just types and no code. That's because monads and comonads are abstract classes (although Haskell probably uses a different terminology), whose implementation details are left to subclasses.

Yet types alone can say a lot. If I gave you a mysterious function whose type was (a -> a) and which worked for any possible type a, it would be pretty clear that I gave you the identity function. If I gave you a mysterious function whose type was (a -> (a -> b) -> b) and which worked for any possible types a and b, it would be pretty clear that I gave you a function which applies its second argument to the first. And that's exactly the kind of behaviours which the mysterious return, extract, (>>=), and (=>>) must exhibit, except they must deal with the extra m and w stuff as well.

Let's pause to ponder the apparent futility of a function which applies its second argument to the first. It is exactly like function application, but its arguments are in the opposite order. Why would anyone work backwards like this? Many people, in fact.

python abs(int("-42")) ruby "-42".to_i.abs

So "binding a value to a function" is just functional-programming jargon for the standard object-oriented way of chaining computations together. What is incredible with monads and comonads is that each different m and w provides a slightly different behaviour for this dot operator. Who knew we could need more than one?

A concrete monad or comonad implementation of a binding arrow, thus, simply applies its second argument to the first, and deals with the extra m or w stuff as well. Monad and comonad users must also deal with the extra m and w stuff, because they are the ones writing the (a -> m b) and (w a -> b) functions which (>>=) and (=>>) expect. Fortunately for them, return and extract can be used to lift their ordinary (a -> b) functions into a suitable form.

haskell lift_m :: Monad m => (a -> b) -> (a -> m b) haskell lift_m f a = return (f a) haskell haskell lift_w :: Comonad w => (a -> b) -> (w a -> b) haskell lift_w f a = f (extract a)

The big difference between using monads, comonads, or the ordinary dot operator is the kind of non-lifted functions which can be used. For the ordinary dot operator there are none, for monads there are functions which can stuff something extra along the return value, and for comonads there are functions which can extract something extra out of the input value.

A spacesuit example should follow soon.

Saturday, October 28, 2006

Monads as Universe Helpers

To see if the grass is greener on the other side of the programming fence, I've been accumulating a scaffolding of knowledge about functional programming which recently culminated in my understanding of the really cool and deep concept of monads.

Monads chain computations in a special way. There are many types of monads, and each chains its computations in its own special way. One of them chains computations in good old procedural fashion, keeping results around inside good old assignable variables and running computations according to our good friends the flow constructs; if, while, for and collaborators. Another familiar face is the layer-based architecture, the bureaucratic tower in which each floor's computation feeds the layer immediately above it. Another monad lets any computation abort part of the remaining computational chain by throwing an exception, and yet another generalizes the idea to the also-quite-cool concept of continuations.

Now, given that I already knew these useful models, was it really worth it to bang my head against the wall until monads finally made sense? Well, walls as just as rigid you would imagine them to be, so I would not recommend doing so if you're only looking for a new tool to add in your ever-growing bag of programming tricks. I believe the concept is more of a philosophical tool, actually. Or perhaps a mathematician's cute pet.

Remember what happened last time philosophers played with the concept of computability? Their fellow physicists had experimented with the world and made very impressive predictions about the behaviour of thrown balls and sliding blocks and complicated pulley systems. What do you know, it seems the world might be computable after all; determinism enters the scene and turns us all into emotionless mechanical contraptions.

The mechanical contraptions cried and argued against their fate, ridiculed defenceless pulley systems so they would feel better about themselves, and actively looked for escape clauses in their contract with the universe. Then one triumphal morning, quantum mechanics defeated the crooked determinism beast by invalidating science's dream of perfectly accurate predictions. What a relief; we could now choose to be emotionless lottery contraptions if we so wished. But of course what we really wanted all along was free will, not unpredictability.

Enter monads. And markov chains. You are an anthropomorphic dot moving in a large graph representing the universe. At each time-step, you must choose one of your node's outgoing edges and follow it to a new node. Some universe implementations may be deterministic or random about this and you won't be left with much of a choice. Some implementations might be kind enough to give you free will. But the gods who subcontracted the construction of the universe to our firm aren't quite sure which it will be yet, so we better make our implementation modular in that respect.

We also better make sure that our implementation can scale well, because universes tend to grow astronomically large. For one thing, we definitely don't want to fill up drive after drive with nodes and edges, so we will need to use a trick. We will only keep track of your current node and compute your possible outgoing edges, on the fly, whenever we need them. The architecture of our universe is thus composed of three parts: a function mapping nodes to possible actions (Laws), a current node (Creature), and that thing which chains executions of the functions using either free will or not (Monad). Yes, a free will monad; I told you this stuff was deep.