Unlike a Functor, a Monad can change shape?

2019-04-03 20:51发布

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.

8条回答
劫难
2楼-- · 2019-04-03 21:37

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 and Functor 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 is m a for some Monad m of kind * -> * and some other type of kind *. I'm not entirely sure what to call Functor 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 between fmap and >>=, or even between f and g, rather than between Monad and Functor.

查看更多
SAY GOODBYE
3楼-- · 2019-04-03 21:39

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 of g 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 and join instead of >>=. Together with return, either way gives an equivalent definition of a monad. In the fmap/join view, though, what is happening here is more clear. Continuing with your list example, first g is fmapped over the list yielding [[1],[],[3]]. Then that list is joined, which for list is just concat.

查看更多
登录 后发表回答