Talk:Monads as containers
I am reading this article for the N-th time now, and though I'm quite a bit clearer on the subject, there still appears to be something at the corner of the picture that eludes me. Treating Monads as either Containers or Computations makes perfect sense, each on its own, but both ways brought together are void, somehow.
On the one hand, once you understand
bind, it seems to be more basic than
map, and its definition in terms of those seems a bit
far-fetched. On the other hand,
bind presents a significant semantic
threshold to overcome before you can say you understand what it does, while
map are much more straightforward.
Thus, the real problem seems to be reconciling both views. How about a "Computations as Containers" article? :)
Sorry for the late reply, I hadn't noticed this change until now. The basic connection is that a (parameterless) computation is a container for its results. To pass from a value of type (m a) to one of type a, possibly special circumstances will be necessary and extra context supplied to run the computation and extract the result from the computation/container. Sometimes, this will be statically impossible, as is the case with the IO monad.
The container/computation duality is a bit odd at first, but if you look at enough parallels in how things work, you'll essentially come to see it as just two ways of thinking about the same thing. Neither view is really perfect, but the correct description is rather abstract, so to work with it, you want some intuitive picture of what's happening.
Perhaps some clarification as to what
map do for computations
would be in order. I agree that for computations, bind is more
primitive-seeming than map and join, but map and join aren't all that bad
either. Map is fairly easy - mapping f over some computation gives the
computation which returns f of the result of the original computation. In
do-notation, this looks like: (I have something like this above, but here it
has the interpretive comments)
fmap f x = do r <- x -- run x return (f r) -- apply f to its result before returning it.
Join is conceptually slightly more bizarre, but still not so bad if you look
at the types:
join :: (Monad m) => m (m a) -> m a. It takes a computation
whose result is a computation, and produces the computation which runs that
computation and then runs its result, removing the level of indirection. So
in do notation, that looks like:
join x = do a <- x -- run x, getting its result, a r <- a -- run a, getting its result, r return r
or more simply:
join x = do a <- x -- run x a -- run its result
Looking at those examples is a good way to set up a correspondence of what's going on with the computations and what's going on with the containers. I'm not sure how much this helps, but really, being able to switch back and forth between the two analogies is enough to get a lot done.
hope this helps,
It does help, as far as it points the finger at the key notions to consider. The relationship is still elusive though, and I am growing ever more likely to think that the only proper way to appreciate something as fundamental as this is by rumination (I was hoping I could be spared of that :) This is partially reinforced by my impression that the topic seems to be the source of the same amount of confusion for just about anyone else who has approached it for the first time.
Thanks for the tips.
Well, the connection between the two analogies is important, but it's good to remember that you only really need to think of it in one way at a time. It's possible to get things done even if you only know one way to think about things. The reason that I put this page up is that I found that at first the idea of monads as abstractions of computation didn't work so well for me, so I needed to figure out my own analogy for the way that things worked, and containers is what worked for me. If you can understand the analogies separately, then you should have enough material to get work done, and together with a little experience in using them, to create your own monads. :)