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.
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.
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 by fmap
, the unchanging parts such as the tuple constructor in State
, 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 in Reader
).
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 two Functors
independently, without letting the latter influence the former.
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.
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 is return
, which by definition creates whatever shape is necessary such that (>>= return)
is the identity function. The definition of a Monad
tells you what you can do, given certain kinds of functions; it doesn't provide those functions for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With