On the signature of >>= Monad operator

后端 未结 6 2331
粉色の甜心
粉色の甜心 2021-02-14 13:54

This is the signature of the well know >>= operator in Haskell

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

The question is why

相关标签:
6条回答
  • 2021-02-14 14:22

    The reason is that (>>=) is more general. The function you're suggesting is called liftM and can be easily defined as

    liftM :: (Monad m) => (a -> b) -> (m a -> m b)
    liftM f k  =  k >>= return . f
    

    This concept has its own type class called Functor with fmap :: (Functor m) => (a -> b) -> (m a -> m b). Every Monad is also a Functor with fmap = liftM, but for historical reasons this isn't (yet) captured in the type-class hierarchy.

    And adapt you're suggesting can be defined as

    adapt :: (Monad m) => (a -> b) -> (a -> m b)
    adapt f = return . f
    

    Notice that having adapt is equivalent to having return as return can be defined as adapt id.

    So anything that has >>= can also have these two functions, but not vice versa. There are structures that are Functors but not Monads.

    The intuition behind this difference is simple: A computation within a monad can depend on the results of the previous monads. The important piece is (a -> m b) which means that not just b, but also its "effect" m b can depend on a. For example, we can have

    import Control.Monad
    
    mIfThenElse :: (Monad m) => m Bool -> m a -> m a -> m a
    mIfThenElse p t f = p >>= \x -> if x then t else f
    

    but it's not possible to define this function with just Functor m constraint, using just fmap. Functors only allow us to change the value "inside", but we can't take it "out" to decide what action to take.

    0 讨论(0)
  • 2021-02-14 14:28

    I had the same question for a while and was thinking why bother with a -> m b once mapping a -> b to m a -> m b looks more natural. This is simialr to asking "why we need a monad given the functor".

    The little answer that I give to myself is that a -> m b accounts for side-effects or other complexities that you would not capture with function a -> b.

    Even better wording form here (highly recommend):

    monadic value M a can itself be seen as a computation. Monadic functions represent computations that are, in some way, non-standard, i.e. not naturally supported by the programming language. This can mean side effects in a pure functional language or asynchronous execution in an impure functional language. An ordinary function type cannot encode such computations and they are, instead, encoded using a datatype that has the monadic structure.

    I'd put emphasis on ordinary function type cannot encode such computations, where ordinary is a -> b.

    0 讨论(0)
  • 2021-02-14 14:29

    Basically, (>>=) lets you sequence operations in such a way that latter operations can choose to behave differently based on earlier results. A more pure function like you ask for is available in the Functor typeclass and is derivable using (>>=), but if you were stuck with it alone you'd no longer be able to sequence operations at all. There's also an intermediate called Applicative which allows you to sequence operations but not change them based on the intermediate results.

    As an example, let's build up a simple IO action type from Functor to Applicative to Monad.


    We'll focus on a type GetC which is as follows

    GetC a = Pure a | GetC (Char -> GetC a)
    

    The first constructor will make sense in time, but the second one should make sense immediately—GetC holds a function which can respond to an incoming character. We can turn GetC into an IO action in order to provide those characters

    io :: GetC a -> IO a
    io (Pure a)  = return a
    io (GetC go) = getChar >>= (\char -> io (go char))
    

    Which makes it clear where Pure comes from---it handles pure values in our type. Finally, we're going to make GetC abstract: we're going to disallow using Pure or GetC directly and allow our users access only to functions we define. I'll write the most important one now

    getc :: GetC Char
    getc = GetC Pure
    

    The function which gets a character then immediately considers is a pure value. While I called it the most important function, it's clear that right now GetC is pretty useless. All we can possibly do is run getc followed by io... to get an effect totally equivalent to getChar!

    io getc        ===     getChar     :: IO Char
    

    But we'll build up from here.


    As stated at the beginning, the Functor typeclass provides a function exactly like you're looking for called fmap.

    class Functor f where
      fmap :: (a -> b) -> f a -> f b
    

    It turns out that we can instantiate GetC as a Functor so let's do that.

    instance Functor GetC where
      fmap f (Pure a)  = Pure (f a)
      fmap f (GetC go) = GetC (\char -> fmap f (go char))
    

    If you squint, you'll notice that fmap affects the Pure constructor only. In the GetC constructor it just gets "pushed down" and deferred until later. This is a hint as to the weakness of fmap, but let's try it.

    io                       getc  :: IO Char
    io (fmap ord             getc) :: IO Int
    io (fmap (\c -> ord + 1) getc) :: IO Int
    

    We've gotten the ability to modify the return type of our IO interpretation of our type, but that's about it! In particular, we're still limited to getting a single character and then running back to IO to do anything interesting with it.

    This is the weakness of Functor. Since, as you noted, it deals only with pure functions it gets stuck "at the end of a computation" modifying the Pure constructor only.


    The next step is Applicative which extends Functor like this

    class Functor f => Applicative f where
      pure  :: a -> f a
      (<*>) :: f (a -> b) -> f a -> f b
    

    In other words it extends the notion of injecting pure values into our context and allowing pure function application to cross over the data type. Unsurprisingly, GetC instantiates Applicative too

    instance Applicative GetC where
      pure = Pure
      Pure f   <*> Pure x   = Pure (f x)
      GetC gof <*> getcx    = GetC (\char -> gof <*> getcx)
      Pure f   <*> GetC gox = GetC (\char -> fmap f (gox char))
    

    Applicative allows us to sequence operations and that might be clear from the definition already. In fact, we can see that (<*>) pushes character application forward so that the GetC actions on either side of (<*>) get performed in order. We use Applicative like this

    fmap (,) getc <*> getc :: GetC (Char, Char)
    

    and it allows us to build incredibly interesting functions, much more complex than just Functor would. For instance, we can already form a loop and get an infinite stream of characters.

    getAll :: GetC [Char]
    getAll = fmap (:) getc <*> getAll
    

    which demonstrates the nature of Applicative being able to sequence actions one after another.

    The problem is that we can't stop. io getAll is an infinite loop because it just consumes characters forever. We can't tell it to stop when it sees '\n', for instance, because Applicatives sequence without noticing earlier results.


    So let's go the final step an instantiate Monad

    instance Monad GetC where
      return = pure
      Pure a  >>= f = f a
      GetC go >>= f = GetC (\char -> go char >>= f)
    

    Which allows us immediately to implement a stopping getAll

    getLn :: GetC String
    getLn = getc >>= \c -> case c of
      '\n' -> return []
      s    -> fmap (s:) getLn
    

    Or, using do notation

    getLn :: GetC String
    getLn = do
      c <- getc
      case c of
        '\n' -> return []
        s    -> fmap (s:) getLn
    

    So what gives? Why can we now write a stopping loop?

    Because (>>=) :: m a -> (a -> m b) -> m b lets the second argument, a function of the pure value, choose the next action, m b. In this case, if the incoming character is '\n' we choose to return [] and terminate the loop. If not, we choose to recurse.

    So that's why you might want a Monad over a Functor. There's much more to the story, but those are the basics.

    0 讨论(0)
  • 2021-02-14 14:29

    As others have said, your bind is the fmap function of the Functor class, a.k.a <$>.

    But why is it less powerful than >>=?

    it seems not difficult to write a general "adapter"

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

    You can indeed write a function with this type:

    adapt f x = return (f x)
    

    However, this function is not able to do everything that we might want >>='s argument to do. There are useful values that adapt cannot produce.

    In the list monad, return x = [x], so adapt will always return a single-element list.

    In the Maybe monad, return x = Some x, so adapt will never return None.

    In the IO monad, once you retrieved the result of an operation, all you can do is compute a new value from it, you can't run a subsequent operation!

    etc. So in short, fmap is able to do fewer things than >>=. That doesn't mean it's useless -- it wouldn't have a name if it was :) But it is less powerful.

    0 讨论(0)
  • 2021-02-14 14:31

    The whole 'point' of the monad really (that puts it above functor or applicative) is that you can determine the monad you 'return' based on the values/results of the left hand side.

    For example, >>= on a Maybe type allows us to decide to return Just x or Nothing. You'll note that using functors or applicative, it is impossible to "choose" to return Just x or Nothing based on the "sequenced" Maybe.

    Try implementing something like:

    halve :: Int -> Maybe Int
    halve n | even n    = Just (n `div` 2)
            | otherwise = Nothing
    
    return 24 >>= halve >>= halve >>= halve
    

    with only <$> (fmap1) or <*> (ap).

    Actually the "straightforward integration of pure code" that you mention is a significant aspect of the functor design pattern, and is very useful. However, it is in many ways unrelated to the motivation behind >>= --- they are meant for different applications and things.

    0 讨论(0)
  • 2021-02-14 14:46

    I think that J. Abrahamson's answer points to the right reason:

    Basically, (>>=) lets you sequence operations in such a way that latter operations can choose to behave differently based on earlier results. A more pure function like you ask for is available in the Functor typeclass and is derivable using (>>=), but if you were stuck with it alone you'd no longer be able to sequence operations at all.

    And let me show a simple counterexample against >>= :: Monad m => m a -> (a -> b) -> m b.

    It is clear that we want to have values bound to a context. And perhaps we will need to sequentially chain functions over such "context-ed values". (This is just one use case for Monads).

    Take Maybe simply as a case of "context-ed value".

    Then define a "fake" monad class:

    class Mokad m where  
         returk :: t -> m t  
         (>>==) :: m t1 -> (t1 -> t2) -> m t2
    

    Now let's try to have Maybe be an instance of Mokad

    instance Mokad Maybe where
             returk x = Just x
             Nothing >>== f = Nothing
             Just x >>== f = Just (f x) -- ????? always Just ?????
    

    The first problem appears: >>== is always returning Just _.

    Now let's try to chain functions over Maybe using >>== (we sequentially extract the values of three Maybes just to add them)

    chainK :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int
    chainK ma mb mc = md 
          where
            md = ma >>== \a -> mb >>== \b -> mc >>== \c -> returk $ a+b+c
    

    But, this code doesn't compile: md type is Maybe (Maybe (Maybe Int)) because every time >>== is used, it encapsulates the previous result into a Maybe box.

    And on the contrary >>= works fine:

    chainOK :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int
    chainOK ma mb mc = md 
          where
            md = ma >>= \a -> mb >>= \b -> mc >>= \c -> return (a+b+c)  
    
    0 讨论(0)
提交回复
热议问题