Unlike a Functor, a Monad can change shape?

前端 未结 8 2382
太阳男子
太阳男子 2021-02-18 21:29

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

相关标签:
8条回答
  • 2021-02-18 21:51

    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:

    newtype Identity a = Identity a
    instance Monad Identity where
        return = Identity
        Identity a >>= f = f a
    

    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, or IO monads?

    0 讨论(0)
  • 2021-02-18 21:51

    The simplest type of a function satisfying the requirement I can imagine is this:

    enigma :: Monad m => m () -> m ()
    

    One can implement it in one of the following ways:

    enigma1 m = m -- not changing the shape
    
    enigma2 _ = return () -- changing the shape
    

    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:

    foo :: Monad m => m () -> m () -> m ()
    foo a b = a >> b
    

    The result of foo can have shape different from both a and b.

    A third obvious change of shape, requiring the full power of the monad, is a

    join :: Monad m => m (m a) -> m a
    join x = x >>= id
    

    The shape of join x is usually not the same as of x itself.

    Combining those primitive changes of shape, one can derive non-trivial things like sequence, foldM and alike.

    0 讨论(0)
  • 2021-02-18 21:55

    Does

    h :: (Monad m, Num a) => a -> m a
    h 0 = fail "Failed."
    h a = return a
    

    suit your needs? For example,

    > [0,1,2,3] >>= h
    [1,2,3]
    
    0 讨论(0)
  • 2021-02-18 21:59

    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:

    (>>=) :: Monad m => m a -> (a -> m b) -> m b
    

    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.

    0 讨论(0)
  • 2021-02-18 22:02

    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, or join. Also, when you use liftM or fmap 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 write get = \s -> (s, s), but without being able to access the state, there's not much point to being in the monad.

    0 讨论(0)
  • 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.

    0 讨论(0)
提交回复
热议问题