0

>>= has type m a -> (a -> m b) -> m b

Suppose ma :: m a and f :: a -> m b.

What is the purpose of >>= :: m a -> (a -> m b) -> m b more about:

  • either being able to implicitly unpack ma :: m a to a so that f :: a -> m b can apply to it?
  • Or being able to handle different cases depending on ma :: m a? Many implementations of >>= check argument ma to see if ma satisfies some condition. If yes, then a <- ma; f a. If no, then do something else with ma, say g ma, where g :: m a -> m b. Is it correct that a more specific and still equivalent version of >>= would have a type m a -> (a ->m b) -> (... -> m b) -> m b, where ... -> m b is the type for the third argument like g above? If yes, what is type ...? Shall it be m a? It might not be a, correct?

  • Or both?

Thanks.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
Tim
  • 1
  • 122
  • 314
  • 481
  • 2
    No, `>>=` does not implicitly takes another argument. The logic is implemented in the `>>=` (bind) function. Note that a lot of monad instances do not make such check. For example the list monad, state monad, etc. – Willem Van Onsem Jul 25 '19 at 12:27
  • Thanks. Sorry for my language. I mean ?Can `>>=` be viewed as equivalently and implicitly take other function argument(s) for dealing with alternative case(s)?? If a monad instance doesn't check alternative cases, then is it the same as just pass a null function as the additional argument? – Tim Jul 25 '19 at 12:28
  • But the "fail" option is not parameterized. For a `Maybe` monad, if the left operand is `Nothing` it is logical to return `Nothing`. What else would it return? Since it has no `x`, we can not apply a function on that `x`. For several monad instances, the `>>=` is the only possible implementation (w.r.t. the types), or (one of) the most simplest implementation. Certain tools like Djinn/Exference/... can frequently "guess" the monad implementation. – Willem Van Onsem Jul 25 '19 at 12:30
  • "*Many implementations*" [citation needed] – melpomene Jul 25 '19 at 12:30
  • 4
    Could you try to reformulate your question in a different way than "Can `>>=` be viewed as equivalently..."? You also used that formulation in a previous question and it makes it unclear whether you are asking a technical or conceptual question. – Li-yao Xia Jul 25 '19 at 12:36
  • 10
    You seem to be asking a lot of questions, some of which display confusion. Instead of asking more questions, perhaps you should instead try some exercises involving monads—the best way to learn is doing—and you may find your understanding is improved. – AJF Jul 25 '19 at 12:39
  • @WillemVanOnsem Thanks. What is `>>= :: m a -> (a ->m b) -> m b` more about: being able to implicitly unpack `ma :: m a` to `a` so that `f :: a-> mb` can apply to it, or being able to handle different cases depending on `ma :: m a`, or both? – Tim Jul 25 '19 at 13:02
  • @melpomene See my update. I guess I might mistaken the purpose of >>= to be handling different cases depending on argument. – Tim Jul 25 '19 at 13:14
  • 2
    @Tim Yes, both. Part of "unpacking" (which isn't implicit; it's right there in the definition of `>>=`) means being able to unpack *any* value of type `m a`, no matter which constructor of was used to create it. – chepner Jul 25 '19 at 13:17
  • @chepner Thanks. Some instance types don't handle more than one cases, so is >>= actually not about handling more than one cases depending on the first argument, but only about unpacking the first argument so that the second function argument can apply to it? – Tim Jul 25 '19 at 13:19
  • 1
    Agreed that the "unpacking" is definitely the key to `>>=`. I would say that the key reason why monads are useful is that they allow you to compose functions. You can easily compose function types `a -> b` and `b -> c`, but in practice you probably have `a -> m b` and `b -> m c` instead, to allow for some context (eg possible failure, need to rely on some environment, etc). You can no longer compose these directly, but you can use `>>=` to effectively do so, and get a function of type `a -> m c`. This is explained in all the better monad tutorials, including plenty of SO answers. – Robin Zigmond Jul 25 '19 at 13:24
  • @Tim What "cases" are you talking about? I assumed you meant (using `[]` as an example) defining `(>>=)` to handle both `[]` and `(x:xs)`. – chepner Jul 25 '19 at 13:26
  • @chepner for list monad, >>= only handles one case `xs >>= f = [y | x – Tim Jul 25 '19 at 13:31
  • @RobinZigmond Many monad tutorials use the example of `Maybe` monad, whose >>= handles different cases depending on its first argument. Is it misleading to think that the purpose of >>= involves handling different cases depending on the first argument? – Tim Jul 25 '19 at 13:33
  • 1
    @Tim I would call that an equivalence, not an actual definition of `(>>=)` (because it assumes some definition of list comprehensions that isn't itself based on `(>>=)`). The more "proper" definition would be `[] >>= f = []` and `(x:xs) >>= f = f x : xs >>= f`. – chepner Jul 25 '19 at 13:36
  • 1
    @Tim: no, it handles both data constructors, since if the list is empty, `[ y | x – Willem Van Onsem Jul 25 '19 at 13:39
  • See https://www.haskell.org/onlinereport/exps.html#sect3.11 for what a list comprehension really means. Essentially, it just requires that it be equivalent to an application of `concatMap`, however `concatMap` is defined. An implementation is therefore free to define list comprehensions as a "primitive" operation, and define `concatMap` and/or `(>>=)` in terms of a list comprehension, or it can make `concatMap` the "primitive" and define list comprehensions as syntactic sugar. – chepner Jul 25 '19 at 13:45
  • 3
    @Tim yes it is misleading if that's your takeaway. It's quite incidental whether a particular implementation of `>>=` does different things for different constructors (in fact a Monad can do anything it likes as long as it respects the monad laws, it's just an abstract interface rather like interfaces in classical OOP). As I said, the real *raison d'etre* of `>>=` is to compose functions which produce Monadic results. – Robin Zigmond Jul 25 '19 at 13:45
  • 1
    I think this represents a misunderstanding: "Many implementations of `>>=` check argument ma to see if ma satisfies some condition. If yes, then `a >=`. The fundamental thing here is `(>>=)`, **not** ` – David Young Jul 25 '19 at 16:57
  • @RobinZigmond I'm not sure I would say unpacking is key to `>>=`, since are some `Monad`s where `>>=` does not do any unpacking (`Const a` and `Proxy` definitely don't. Also, in my opinion at least, it's a bit of a stretch to say that `Monad`s like `(->) a` do unpacking (an unnecessary stretch at that, again in my opinion. Particularly since there are other `Monad`s which definitely do not unpack)). – David Young Jul 25 '19 at 17:08
  • Also, one more `Monad` where the "unpacking" concept doesn't work too well: `IO`. In `x >>= f` for `IO`, the value you give the function argument is not "packed" inside the `x` argument. It executes the argument and gets the result from that, then passes it to `f`. This is sort of like executing `ls | wc` in Bash or zsh (etc). You wouldn't generally say that the list of filenames in the current directory is contained in `/bin/ls`, and then `/bin/ls` was "unpacked" by the pipe. – David Young Jul 25 '19 at 17:13
  • 1
    @David perhaps "unpacking" isn't the most accurate word (I didn't choose it), but I still think this is the key thing about `>>=`, at least when viewed through from a sufficiently abstract viewpoint. But `IO` is a great example of the practical use of this "unpacking" imo. When you write `x >>= f`, you can't at that point possibly know the result of the `IO` action `x`, but you can still "unpack" the value (that is, actually run the IO action) and feed it to `f`. (Please forgive my drift into imperative-style language.) – Robin Zigmond Jul 25 '19 at 17:34
  • the purpose of >>= is to apply the function to the value under wraps, and how it does it, involves handling different cases depending on the first argument. about lists, it's `[] >>= f = [] ; (x:xs) >>= f = f x ++ xs >>= f`. – Will Ness Jul 26 '19 at 10:46

1 Answers1

1

The first. The purpose of ma >>= k is to apply the function k to the a value under m wraps, and how it does it, involves handling different cases depending on the first argument.

The type "mandala" for ma >>= k = join (fmap k ma) is

    m   a                   -- ma
        a  ->   m b         -- k
    m          (m b)        -- after fmap
   ------------------
    m             b         -- after join

k :: a -> m b is indeed applied to a "inside" m a, "under wraps".

The checking of ma value that you mention is an orthogonal issue. If m a is a sum type, >>= will check and see which of the alternatives to handle; the type handled is still the same m a. For example, Nothing still has the type Maybe a, even if it has no specific a value "inside".

Both Nothing and Just x cases are handled by the same <- (or equivalently >>=),

    do { a <- ma ; foo a }  ==  ma >>= (\a -> foo a)
                            ==  (\case Nothing -> Nothing ;
                                       Just a  -> (\a -> foo a) a) ma

Another part of your second option is a reflection on the possibility of chaining.

Using the so called "Kleisli composition"

(f >=> g) x  =  f x >>= g

we can chain "Kleisli arrow" type of functions,

   f  >=>  g  >=>  ...  >=>  h
------------------------------------
 a -> m b
        b -> m c
                  ......
                           s -> m t
------------------------------------
 a ->                           m t

But both >>= and >=> are binary operators. When the types align, they can be chained, to form n-ary chains of operations; but the basic, elementary operator is binary.

This is similar to lists, formed with the binary operator (:).

[a,b,c,...,n]  =  a : (b : (c : ... (n : []) ...))
Will Ness
  • 62,652
  • 8
  • 86
  • 167