Monads with Join() instead of Bind()
Monads are usually explained in turns of return
and bind
. However, I gather you can also implement bind
in terms of join
(and fmap
?)
In programming languages lacking first-class functions, bind
is excruciatingly awkward to use. join
, on the other hand, looks quite easy.
I'm not completely sure I understand how join
works, however. Obviously, it has the [Haskell] type
join :: Monad m => m (m x) -> m x
For the list monad, this is trivially and obviously concat
. But for a general monad, what, operationally, does this method actually do? I see what it does to the type signatures, but I'm trying to figure out how I'd write something like this in, say, Java or similar.
(Actually, that's easy: I wouldn't. Because generics is broken. ;-) But in principle the question still stands...)
Oops. It looks like this has been asked before:
Monad join function
Could somebody sketch out some implementations of common monads using return
, fmap
and join
? (Ie, not mentioning >>=
at all.) I think perhaps that might help it to sink in to my dumb brain...
Without plumbing the depths of metaphor, might I suggest to read a typical monad m
as "strategy to produce a", so the type m value
is a first class "strategy to produce a value". Different notions of computation or external interaction require different types of strategy, but the general notion requires some regular structure to make sense:
return :: v -> mv
) consisting of nothing other than producing the value that you have; fmap :: (v -> u) -> mv -> mu
) just by waiting for the strategy to deliver its value, then transforming it; join :: m (mv) -> mv
) which follows the outer strategy until it produces the inner strategy, then follows that inner strategy all the way to a value. Let's have an example: leaf-labelled binary trees...
data Tree v = Leaf v | Node (Tree v) (Tree v)
...represent strategies to produce stuff by tossing a coin. If the strategy is Leaf v
, there's your v
; if the strategy is Node ht
, you toss a coin and continue by strategy h
if the coin shows "heads", t
if it's "tails".
instance Monad Tree where
return = Leaf
A strategy-producing strategy is a tree with tree-labelled leaves: in place of each such leaf, we can just graft in the tree which labels it...
join (Leaf tree) = tree
join (Node h t) = Node (join h) (join t)
...and of course we have fmap
which just relabels leaves.
instance Functor Tree where
fmap f (Leaf x) = Leaf (f x)
fmap f (Node h t) = Node (fmap f h) (fmap f t)
Here's an strategy to produce a strategy to produce an Int
.
Toss a coin: if it's "heads", toss another coin to decide between two strategies (producing, respectively, "toss a coin for producing 0 or producing 1" or "produce 2"); if it's "tails" produce a third ("toss a coin for producing 3 or tossing a coin for 4 or 5").
That clearly join
s up to make a strategy producing an Int
.
What we're making use of is the fact that a "strategy to produce a value" can itself be seen as a value. In Haskell, the embedding of strategies as values is silent, but in English, I use quotation marks to distinguish using a strategy from just talking about it. The join
operator expresses the strategy "somehow produce then follow a strategy", or "if you are told a strategy, you may then use it".
(Meta. I'm not sure whether this "strategy" approach is a suitably generic way to think about monads and the value/computation distinction, or whether it's just another crummy metaphor. I do find leaf-labelled tree-like types a useful source of intuition, which is perhaps not a surprise as they're the free monads, with just enough structure to be monads at all, but no more.)
PS The type of "bind"
(>>=) :: m v -> (v -> m w) -> m w
says "if you have a strategy to produce a v
, and for each va follow-on strategy to produce a w
, then you have a strategy to produce a w
". How can we capture that in terms of join
?
mv >>= v2mw = join (fmap v2mw mv)
We can relabel our v
-producing strategy by v2mw
, producing instead of each v
value the w
-producing strategy which follows on from it — ready to join
!
join = concat -- []
join f = x -> f x x -- (e ->)
join f = s -> let (f', s') = f s in f' s' -- State
join (Just (Just a)) = Just a; join _ = Nothing -- Maybe
join (Identity (Identity a)) = Identity a -- Identity
join (Right (Right a)) = Right a; join (Right (Left e)) = Left e;
join (Left e) = Left e -- Either
join ((a, m), m') = (a, m' `mappend` m) -- Writer
join f = k -> f (f' -> f' k) -- Cont
OK, so it's not really good form to answer your own question, but I'm going to note down my thinking in case it enlightens anybody else. (I doubt it...)
If a monad can be thought of as a "container", then both return
and join
have pretty obvious semantics. return
generates a 1-element container, and join
turns a container of containers into a single container. Nothing hard about that.
So let us focus on monads which are more naturally thought of as "actions". In that case, mx
is some sort of action which yields a value of type x
when you "execute" it. return x
does nothing special, and then yields x
. fmap f
takes an action that yields an x
, and constructs an action that computes x
and then applies f
to it, and returns the result. So far, so good.
It's fairly obvious that if f
itself generates an action, then what you end up with is m (mx)
. That is, an action that computes another action. In a way, that's maybe even simpler to wrap your mind around than the >>=
function which takes an action and a "function that produces an action" and so on.
So, logically speaking, it seems join
would run the first action, take the action it produces, and then run that. (Or rather, join
would return an action that does what I just described, if you want to split hairs.)
That seems to be the central idea. To implement join
, you want to run an action, which then gives you another action, and then you run that. (Whatever "run" happens to mean for this particular monad.)
Given this insight, I can take a stab at writing some join
implementations:
join Nothing = Nothing
join (Just mx) = mx
If the outer action is Nothing
, return Nothing
, else return the inner action. Then again, Maybe
is more of a container than an action, so let's try something else...
newtype Reader s x = Reader (s -> x)
join (Reader f) = Reader ( s -> let Reader g = f s in g s)
That was... painless. A Reader
is really just a function that takes a global state and only then returns its result. So to unstack, you apply the global state to the outer action, which returns a new Reader
. You then apply the state to this inner function as well.
In a way, it's perhaps easier than the usual way:
Reader f >>= g = Reader ( s -> let x = f s in g x)
Now, which one is the reader function, and which one is the function that computes the next reader...?
Now let's try the good old State
monad. Here every function takes an initial state as input but also returns a new state along with its output.
data State s x = State (s -> (s, x))
join (State f) = State ( s0 -> let (s1, State g) = f s0 in g s1)
That wasn't too hard. It's basically run followed by run.
I'm going to stop typing now. Feel free to point out all the glitches and typos in my examples... :-/
链接地址: http://www.djcxy.com/p/47666.html上一篇: 他们在哪里需要?