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.
The simplest type of a function satisfying the requirement I can imagine is this:
One can implement it in one of the following ways:
This was a very simple change --
enigma2
just discards the shape and replaces it with the trivial one. Another kind of generic change is combining two shapes together:The result of
foo
can have shape different from botha
andb
.A third obvious change of shape, requiring the full power of the monad, is a
The shape of
join x
is usually not the same as ofx
itself.Combining those primitive changes of shape, one can derive non-trivial things like
sequence
,foldM
and alike.Just because the monad pattern includes some particular instances that allow shape changes doesn't mean every instance can have shape changes. For example, there is only one "shape" available in the
Identity
monad:In fact, it's not clear to me that very many monads have meaningful "shape"s: for example, what does shape mean in the
State
,Reader
,Writer
,ST
,STM
, orIO
monads?A function with a signature like
h
indeed cannot do many interesting things beyond performing some arithmetic on its argument. So, you have the correct intuition there.However, it might help to look at commonly used libraries for functions with similar signatures. You'll find that the most generic ones, as you'd expect, perform generic monad operations like
return
,liftM
, orjoin
. Also, when you useliftM
orfmap
to lift an ordinary function into a monadic function, you typically wind up with a similarly generic signature, and this is quite convenient for integrating pure functions with monadic code.In order to use the structure that a particular monad offers, you inevitably need to use some knowledge about the specific monad you're in to build new and interesting computations in that monad. Consider the state monad,
(s -> (a, s))
. Without knowing that type, we can't writeget = \s -> (s, s)
, but without being able to access the state, there's not much point to being in the monad.You're missing a bit of subtlety here, by the way. For the sake of terminology, I'll divide a
Functor
in the Haskell sense into three parts: The parametric component determined by the type parameter and operated on byfmap
, the unchanging parts such as the tuple constructor inState
, and the "shape" as anything else, such as choices between constructors (e.g.,Nothing
vs.Just
) or parts involving other type parameters (e.g., the environment inReader
).A
Functor
alone is limited to mapping functions over the parametric portion, of course.A
Monad
can create new "shapes" based on the values of the parametric portion, which allows much more than just changing shapes. Duplicating every element in a list or dropping the first five elements would change the shape, but filtering a list requires inspecting the elements.This is essentially how
Applicative
fits between them--it allows you to combine the shapes and parametric values of twoFunctors
independently, without letting the latter influence the former.Perhaps the subtlety you're looking for here is that you're not really "changing" anything. Nothing in a
Monad
lets you explicitly mess with the shape. What it lets you do is create new shapes based on each parametric value, and have those new shapes recombined into a new composite shape.Thus, you'll always be limited by the available ways to create shapes. With a completely generic
Monad
all you have isreturn
, which by definition creates whatever shape is necessary such that(>>= return)
is the identity function. The definition of aMonad
tells you what you can do, given certain kinds of functions; it doesn't provide those functions for you.The key combinator for monads is
(>>=)
. Knowing that it composes two monadic values and reading its type signature, the power of monads becomes more apparent:The future action can depend entirely on the outcome of the first action, because it is a function of its result. This power comes at a price though: Functions in Haskell are entirely opaque, so there is no way for you to get any information about a composed action without actually running it. As a side note, this is where arrows come in.
Does
suit your needs? For example,