问题
The typical monad bind
function has the following signature:
m a -> (a -> m b) -> m b
As I understand it (and I might well be wrong,) the function (a -> m b)
is just a mapping function from one structure a
to another b
. Assuming that is correct begs the question why bind
's signature is not simply:
m a -> (a -> b) -> m b
Given that unit
is part of a monad's definition; why give the function (a -> m b)
the responsibility to call unit
on whatever value b
it produced – wouldn't it be more sensible to make it part of bind
?
回答1:
A function like m a -> (a -> b) -> m b
would be equivalent to fmap :: (a -> b) -> f a -> f b
. All fmap
can do is change the values inside the action, it can't perform new actions. With m a -> (a -> m b) -> m b
, you can "run" the m a
, feed that value into (a -> m b)
, then return a new effect of m b
. Without this, you would only ever be able to have one effect in your program, you couldn't have two print statements sequentially, you couldn't connect to a network and then download a URL, and you couldn't respond to user input, you would only be able to transform the value returned from each primitive operation. It's this operation that allows monads to be more powerful than either functors or applicatives.
Another detail here is that you aren't necessarily just wrapping a value with unit
, that m b
could represent an action, not just returning something. For example, where is the call to return
in the action putStrLn :: String -> m ()
? This function's signature is compatible with the second argument to >>=
, with a ~ String
and b ~ ()
, but there is not call to return
anywhere in its body. The point of >>=
is to sequence two actions together, not just to wrap values in a context.
回答2:
Because m a -> (a -> b) -> m b
is just fmap
which a Monas has, being functor.
What a Monad add to a functor is the ability to join
(or squash) a nested Monad to a simple one.
Example a list of list to simple list , or [[1,2], [3]]
to [1,2,3]
.
If you replace b
with m b
in the fmap
signature you end up with
m a -> (a -> m b) -> m (m b)
With a normal functor, you are stuck with your double layer of container (m (m b)
). With a Monad,
using the join
function, you can squash the m (m b)
to m b
. So bind
is in fact join.fmap
.
In fact, join
and fmap
can be written using only bind
(and return
), so in practice, it's easier to only define one function bind
, instead of two join
and fmap
, even though it often simpler to write the laters.
So basically, bind
is a mix of fmap
and join
.
回答3:
As I understand it (and I might well be wrong,) the function
(a -> m b)
is just a mapping function from one structurea
to anotherb
You're quite right about this – if you change the word "mapping" to morphism. For functions a -> m b
are morphisms of the monad's Kleisli category. In that light, the characteristic feature of monads is that you can compose Kleislis in the same way you can compose functions:
type Kleisli m a b = a -> m b -- `Control.Arrow` has this as a `newtype` with `Category` instance.
-- compare (.) :: (b->c) -> (a->b) -> a->c
(<=<) :: Kleisli m b c -> Kleisli m a b -> Kleisli m a c
(f<=<g) x = f =<< g x
Also, you can use ordinary functions as Kleislis:
(return.) :: (a->b) -> Kleisli m a b
However, Kleislis are strictly more powerful than functions. E.g. for m ≡ IO
, they are basically functions which can have side-effects, which as you know ordinary Haskell functions can't. So you can't turn a Kleisli back into a function – and if >>=
accepted an a->b
rather than a Kleisli m a b
, but all you had was a Kleisli, there would be no way to use it.
回答4:
A function of type a -> m b
has potentially many more capabilities than one of type a -> b
followed by return
(or as you call it, "unit"). In fact no "effectful" operation can be expressed in the latter form.
回答5:
Another take on this: any useful monad will have a number of operations specific to it, beyond just the ones that come from the monadic interface. For example, the IO
monad has getLine :: IO String
. Consider this very simple program:
main :: IO ()
main = do name <- prompt "What's your name?"
putStrLn ("Hello " ++ name ++ "!")
prompt :: String -> IO String
prompt str = do putStrLn str
getLine
Note that the type of prompt
fits the a -> m b
mold, but it doesn't use return
(a.k.a. unit
) anywhere. This is because it uses getLine :: IO String
, an opaque operation provided by the IO
monad and which cannot be defined in terms of return
and >>=
.
Think of it this way: ultimately, Monad
is never something you use on its own; it's an interface for plugging together things that are extrinsic to it, like getLine
and putStrLn
.
来源:https://stackoverflow.com/questions/26846385/why-is-it-binds-arguments-responsibility-to-unit-its-value