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?
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.
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 LocalTime
s 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).
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.