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.
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.
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.
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.
6 comments:
Hmm, surely there has to be more to say than this. What is an example of something one would use a Comonad for?
Comonads are good conversation openers, and with enough practice they can be a great party trick.
Seriously, I haven't had the occasion to put my new understanding to use yet, and I don't think it will happen soon.
I do have a contrived example, though. With an Array comonad, you could run a computation over several values in parallel. At a few key points in the computation, one of those "special functions extracting something something extra out of the input value" could be used to get a glimpse of what the other threads have computed so far.
You should look into zippers for interesting applications of comonads.
OMG I was linked by a long time blog hero of mine, sigfpe! I'm glad somebody decided to clarify my example, I had no clue I was being cryptic.
Hello, fellow sigfpe fans!
The literate haskell on this page renders incorrectly in Opera 9.27.
I'm not sure I understand Comonads... Am I to consider Monads for SISD computations while Comonads are for SIMD? Monad transformers would then be for MISD computations while MIMD is handled by Comonad transformers?
My brow is very furrowed.
Anonymous brow,
monads and comonads are very generic tools, my Array example and sigfpe's similar Pointer example are only one way to use them.
In the world of Array-like type constructors, special monadic operations can return an array of values, whereas special comonadic operations can peek at an array of values. It just happens that for Arrays, a natural implementation for the monadic and comonadic binding arrows is to run the computation once for each value. This doesn't necessarily mean they will run in parallel; that part is up to your compiler.
I'm afraid I don't know about monad transformers yet, so I can't comment on the last half of your comment.
By the way, the CSS should be fixed in Opera now.
Post a Comment