I've always enjoyed the following intuitive explanation of a monad's power relative to a functor: a monad can change shape; a functor cannot.
For example: length $ fmap f [1,2,3]
always equals 3
.
With a monad, however, length $ [1,2,3] >>= g
will often not equal 3
. For example, if g
is defined as:
g :: (Num a) => a -> [a]
g x = if x==2 then [] else [x]
then [1,2,3] >>= g
is equal to [1,3]
.
The thing that troubles me slightly, is the type signature of g
. It seems impossible to define a function which changes the shape of the input, with a generic monadic type such as:
h :: (Monad m, Num a) => a -> m a
The MonadPlus or MonadZero type classes have relevant zero elements, to use instead of []
, but now we have something more than a monad.
Am I correct? If so, is there a way to express this subtlety to a newcomer to Haskell. I'd like to make my beloved "monads can change shape" phrase, just a touch more honest; if need be.
This isn't a full answer, but I have a few things to say about your question that don't really fit into a comment.
Firstly,
Monad
andFunctor
are typeclasses; they classify types. So it is odd to say that "a monad can change shape; a functor cannot." I believe what you are trying to talk about is a "Monadic value" or perhaps a "monadic action": a value whose type ism a
for someMonad m
of kind* -> *
and some other type of kind*
. I'm not entirely sure what to callFunctor f :: f a
, I suppose I'd call it a "value in a functor", though that's not the best description of, say,IO String
(IO
is a functor).Secondly, note that all Monads are necessarily Functors (
fmap = liftM
), so I'd say the difference you observe is betweenfmap
and>>=
, or even betweenf
andg
, rather than betweenMonad
andFunctor
.Monad
's operations can "change the shape" of values to the extent that the>>=
function replaces leaf nodes in the "tree" that is the original value with a new substructure derived from the node's value (for a suitably general notion of "tree" - in the list case, the "tree" is associative).In your list example what is happening is that each number (leaf) is being replaced by the new list that results when
g
is applied to that number. The overall structure of the original list still can be seen if you know what you're looking for; the results ofg
are still there in order, they've just been smashed together so you can't tell where one ends and the next begins unless you already know.A more enlightening point of view may be to consider
fmap
andjoin
instead of>>=
. Together withreturn
, either way gives an equivalent definition of a monad. In thefmap
/join
view, though, what is happening here is more clear. Continuing with your list example, firstg
isfmap
ped over the list yielding[[1],[],[3]]
. Then that list isjoin
ed, which for list is justconcat
.