[Haskell-cafe] evaluation semantics of bind
wren ng thornton
wren at freegeek.org
Mon Feb 9 23:52:31 EST 2009
Gregg Reynolds wrote:
> Tillmann Rendel wrote:
> > An example where it would be wrong to ignore e:
> >
> > sum ([1, 2] >>= const [21])
> >
> > This expression should evaluate to sum [21, 21] = 42, not sum [21] = 21.
> >
>
> Sigh. I hate it when this happens. Just when I thought I had it figured
> out, it turns out I'm clueless. This is very enlightening and should
> definitely be included in any monad tutorial. Actually you don't even need
> "sum" and "const" to demo the point, "[1,2] >>= \x -> [21]" evals to "[21,
> 21]" in ghci. And I have absolutely no idea why. Very mysterious, the
> Kleisli star. :(
Just walk through the evaluation step by step, it's not so mysterious.
[1,2] >>= \x -> [21]
== {CT definition of bind}
join . fmap (\x -> [21]) $ [1,2]
==
join [(\x -> [21]) 1, (\x -> [21]) 2]
==
join [[21],[21]]
==
[21,21]
Or if you prefer to use the Haskell function definitions instead of the
category theory definitions (it's all the same):
[1,2] >>= \x -> [21]
==
concatMap (\x -> [21]) [1,2]
==
concat . map (\x -> [21]) $ [1,2]
==
concat [(\x -> [21]) 1, (\x -> [21]) 2]
==
concat [[21],[21]]
==
[21,21]
This is exactly the point I was raising earlier. The "statefullness" of
monads has nothing to do with IO, but every monad has some of it. In
general it is wrong to throw it away just because the function passed to
bind happens to ignore the value associated with it. There are some
cases where that can be correct, but in general it is not.
For lists, we can envision the statefullness as a path through a
decision tree. To get the intuition right, it's easiest to pretend that
we never call join (or that join does nothing). If we don't call join,
eventually after a number of binds or maps we'll end up with some value
of type [[...[a]...]]. We can draw that value out as a B-tree where each
level has a branch of whatever arity it needs. In this B-tree, every
leaf is associated with a unique path through the tree and therefore
they can be distinguished.
The reason the above example works the way it does is that the initial
list has two leaves each associated with their unique paths through this
tree of choices. The function passed into bind is a continuation of
sorts. So, given that we can non-deterministically choose either of the
paths in the original tree, for each choice we must then continue with
(\x -> [21]) applied to the seed value for that choice (1 or 2, as
appropriate). Because we had two choices initially, and from there we
have only one choice, we will have 2*1 choices in the end:
/\
/ \
(1) (2)
| |
| |
21 21
If we imagine a finite state automaton, we might think that the two 21s
could be collapsed together since they represent the same "state". But
that is not so: the list monad is for *paths* through an FSA not for
states in an FSA. (For states in an FSA we'd want the set monad
instead.) Of course for the list monad we don't actually keep around the
decision tree. In fact lists generalize over all possible decision trees
which could yield the given distribution of path-endpoints, so it's not
quite the way envisioned above.
--
Live well,
~wren
More information about the Haskell-Cafe
mailing list