Why is it bind's argument's responsibility to unit its value?

▼魔方 西西 提交于 2019-12-10 10:38:55

问题


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 structure a to another b

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!