Difference in capability between fmap and bind?

2019-02-06 11:57发布

问题:

I'm new to functional programming (coming from javascript), and I'm having a hard time telling the difference between the two, which is also messing with my understand of functors vs. monads.

Functor:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

Monad (simplified):

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
  • fmap takes a function and a functor, and returns a functor.
  • >>= takes a function and a monad, and returns a monad.

The difference between the two is in the function parameter:

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>= takes a function parameter that returns a monad. I know that this is significant, but I'm having difficulty seeing how this one slight thing makes monads much more powerful than functors. Can someone explain?

回答1:

Well, (<$>) is an alias for fmap, and (=<<) is the same as (>>=) with the arguments swapped:

(<$>) :: (x ->   y) -> b x -> b y
(=<<) :: (x -> b y) -> b x -> b y

The difference is now fairly clear: with the bind function, we apply a function that returns a b y rather than a y. So what difference does that make?

Consider this small example:

foo <$> Just 3

Notice that (<$>) will apply foo to 3, and put the result back into a Just. In other words, the result of this computation cannot be Nothing. On the contrary:

bar =<< Just 3

This computation can return Nothing. (For example, bar x = Nothing will do it.)

We can do a similar thing with the list monad:

foo <$> [Red, Yellow, Blue]   -- Result is guaranteed to be a 3-element list.
bar =<< [Red, Yellow, Blue]   -- Result can be ANY size.

In short, with (<$>) (i.e., fmap), the "structure" of the result is always identical to the input. But with (=<<) (i.e., (>>=)), the structure of the result can change. This allows conditional execution, reacting to input, and a whole bunch of other things.



回答2:

Short answer is that if you can turn m (m a) into m a in a way which makes sense then it's a Monad. This is possible for all Monads but not necessarily for Functors.

I think the most confusing thing is that all of the common examples of Functors (e.g. List, Maybe, IO) are also Monads. We need an example of something that is a Functor but not a Monad.

I'll use an example from a hypothetical calendar program. The following code defines an Event Functor which stores some data that goes with the event and the time that it occurs.

import Data.Time.LocalTime

data Event a = MkEvent LocalTime a

instance Functor Event where
    fmap f (MkEvent time a) = MkEvent time (f a)

The Event object stores the time that the event occurs and some extra data that can be changed using fmap. Now lets try and make it a Monad:

instance Monad Event where
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in
                                MkEvent <notSureWhatToPutHere> b

We find that we can't because you will end up with two LocalTime objects. timeA from the given Event and timeB from the Event given by the result of f a. Our Event type is defined as having only one LocalTime (time) that it occurs at and so making it a Monad isn't possible without turning two LocalTimes into one. (There may be some case where doing so might make sense and so you could turn this into a monad if you really wanted to).



回答3:

Assume for a moment that IO were just a Functor, and not a Monad. How could we sequence two actions? Say, like getChar :: IO Char and putChar :: Char -> IO ().

We could try mapping over getChar (an action that, when executed, reads a Char from stdin) using putChar.

fmap putChar getChar :: IO (IO ())

Now we have a program that, when executed, reads a Char from stdin and produces a program that, when executed, writes the Char to stdout. But what we actually want is a program that, when executed, reads a Char from stdin and writes the Char to stdout. So we need a "flattening" (in the IO case, "sequencing") function with type:

join :: IO (IO ()) -> IO ()

Functor by itself does not provide this function. But it is a function of Monad, where it has the more general type:

join :: Monad m => m (m a) -> m a

What does all of this have to do with >>=? As it happens, monadic bind is just a combination of fmap and join:

:t \m f -> join (fmap f m)
(Monad m) => m a1 -> (a1 -> m a) -> m a

Another way of seeing the difference is that fmap never changes the overall structure of the mapped value, but join (and therefore >>= as well) can do that.

In terms of IO actions, fmap will never cause addicional reads/writes or other effects. But join sequences the read/writes of the inner action after those of the outer action.