7

I have some questions about the definition of the binding function (>>=) in Haskell.

Because Haskell is a pure language, so we can use Monad to handle operations with side effects. I think this strategy is somewhat like putting all actions may cause side effects to another world, and we can control them from our "pure" haskell world though do or >>=.

So when I look at definition of >>= function

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

it takes a (a -> m b) function, so result m a of the former action can be "unpack" to a non-monadic a in >>=. Then the function (a -> m b) takes a as its input and return another monadic m b as its result. By the binding function I can operate on monadic without bringing any side effects into pure haskell codes.

My question is why we use a (a -> m b) function? In my opinion, a m a -> m b function can also do this. Is there any reason, or just because it is designed like this?

EDIT

From comments I understand it's hard to extract a from m a. However, I think I can consider a monadic m a as a a with side effect.

Is it possible to assume function m a -> m b acts similar with a -> b, so we can define m a -> m b like defining a -> b?

Ami Tavory
  • 66,807
  • 9
  • 114
  • 153
calvin
  • 859
  • 9
  • 22
  • 6
    An `m a -> m b` function can't do that because there's no general way to extract an `a` from an `m a`, `IO` being the most common example. – Lee May 22 '17 at 15:56
  • Well usually you cannot get a "value" out of a monad. The state of an I/O monad is for instance your entire system (and the state of the entire file system, the servers on the internet, hardware) at that moment (well virtually of course). – Willem Van Onsem May 22 '17 at 15:56
  • As addition, you can think about `a -> m b` as a callback. When computations will be run it can be called by "interpretator". – freestyle May 22 '17 at 16:00
  • 3
    If it were `m a -> m b`, then we could always let `x >>= f = f x` for any `m`. This would be non interesting: it would not implement the "temporary unboxing" that `>>=` is about, since `f` would not be passed the "unboxed value". – chi May 22 '17 at 16:02
  • To expand the example by Lee: consider `readLn >>= \n -> print (n+10 :: Int)` in the IO monad. This works because `n::Int`, if it were `n :: IO Int` we couldn't add 10 (nor print the result). – chi May 22 '17 at 16:06
  • @Lee I think if a value has type `m a`, it means this value is somewhat "unsafe" because it may cause side effects, apart from this, `m a` is just like `a`, so why can't we assume function `m a -> m b` acts similar with `a -> b`, so we can define `m a -> m b` like defining `a -> b`? – calvin May 22 '17 at 16:13
  • 1
    OK try defining a function of type `IO Int -> IO String` without using `>>=`. – n. 'pronouns' m. May 22 '17 at 16:18
  • 4
    @calvin - The additional power that `Monad` has over `Applicative` is the ability to inspect the inner value `a` to choose the resulting value `m b`. If `(>>=)` took a function `m a -> m b` then you would need a function `m a -> a` to be able to inspect an `a` to decide on which value `m b` to return. However monads do not support such a function. Since monads are functors they already support lifting `a -> b` to `m a -> m b` with `fmap`. – Lee May 22 '17 at 16:24
  • 2
    @Lee *If `(>>=)` took a function `m a -> m b`* then it would be called `(.)`. – n. 'pronouns' m. May 22 '17 at 16:43
  • @n.m. I don't think that is a useful or interesting challenge. `fmap show` is one reasonable example. It's quite easy to do using just Functor. The exciting thing about the Monad interface is that it lets us do *more* than that, by "changing" the IO wrapping our value. – amalloy May 22 '17 at 16:44
  • @amalloy hmmm I'm still living in the pre-AMP universe apparently. – n. 'pronouns' m. May 22 '17 at 16:47
  • @amalloy right; so the question becomes, "can we satisfy ourselves with just `fmap`?" And the answer is of course "no": we'd have to have a _primitive_ `IO a` already defined for us, describing any crazy computation that comes into our minds, to `fmap f` inside it (inside that `IO a` value, describing that crazy computation). (I'm paraphrasing here what you've said). – Will Ness May 22 '17 at 16:49
  • 1
    Possible duplicate of [Difference in capability between fmap and bind?](https://stackoverflow.com/questions/35387237/difference-in-capability-between-fmap-and-bind) – amalloy May 22 '17 at 17:11
  • @n.m. you meant not `(.)`, but `flip ($)` of course (and [`=< – Will Ness May 22 '17 at 17:15
  • @WillNess yeah, sure, my mistake. – n. 'pronouns' m. May 22 '17 at 17:18
  • @n.m. not a mistake, just a minor detail. It *is* about the composition: [`((g <=< f) =<) . join . (f )`](https://stackoverflow.com/questions/11234632/monads-with-join-instead-of-bind/11249714#11249714), and these "promoted" functions do plainly compose. – Will Ness May 22 '17 at 17:23
  • 1
    @calvin About "`m a` is just like `a`" -- no way! Consider `m = []`, the list monad. Is a list of integers the same as an integer? If `m = Maybe`, is an optional integer (which might not be there) the same as an integer? If `m = (-> ) String`, is a function from strings to integers the same as an integer? – chi May 22 '17 at 17:53

1 Answers1

10

edit2: OK, here's what I should've said from the start:

Monads are EDSLs,

E as in embedded domain-specific languages. Embedded means that the language's statements are plain values in our language, Haskell.

Let's try to have us an IO-language. Imagine we have print1 :: IO () primitive, describing an action of printing an integer 1 at the prompt. Imagine we also have print2 :: IO (). Both are plain Haskell values. In Haskell, we speak of these actions. This IO-language still needs to be interpreted / acted upon by some part of the run-time system later, at "run"-time. Having two languages, we have two worlds, two timelines.

We could write do { print1 ; print2 } to describe compound actions. But we can't create a new primitive for printing 3 at the prompt, as it is outside our pure Haskell world. What we have here is an EDSL, but evidently not a very powerful one. We must have an infinite supply of primitives here; not a winning proposition. And it is not even a Functor, as we can't modify these values.

Now, what if we could? We'd then be able to tell do { print1 ; print2 ; fmap (1+) print2 }, to print out 3 as well. Now it's a Functor. More powerful, still not flexible enough.

We get flexibility with primitives for constructing these action descriptors (like print1). It is e.g. print :: Show a => a -> IO a. We can now talk about more versatile actions, like do { print 42; getLine ; putStrLn ("Hello, " ++ "... you!") }.

But now we see the need to refer to the "results" of previous actions. We want to be able to write do { print 42; s <- getLine ; putStrLn ("Hello, " ++ s ++ "!") }. We want to create (in Haskell world) new action descriptions (Haskell values describing actions in IO-world) based on results (in Haskell world) of previous IO-actions that these IO-actions will produce, when they are run, when the IO-language is interpreted (the actions it describes been carried out in IO-world).

This means the ability to create those IO-language statements from Haskell values, like with print :: a -> IO a. And that is exactly the type you're asking about, and it is what makes this EDSL a Monad.


Imagine we have an IO primitive (a_primitive :: IO Int -> IO ()) which prints any positive integer as is, and prints "---" on a separate line before printing any non-positive integer. Then we could write a_primitive (return 1), as you suggest.

But IO is closed; it is impure; we can't write new IO primitives in Haskell, and there can't be a primitive already defined for every new idea that might come into our minds. So we write (\x -> if x > 0 then print x else do { putStrln "---"; print x }) instead, and that lambda expression's type is Int -> IO () (more or less).

If the argument x in the above lambda-expression were of type IO Int the expression x > 0 would be mistyped. There is no way to get that a out of IO a without the use of the standard >>= operator (or its equivalent).

see also:

And, this quote:

"Someone at some point noticed, "oh, in order to get impure effects from pure code I need to do metaprogramming, which means one of my types needs to be 'programs which compute an X'. I want to take a 'program that computes an X' and a function which takes an X and produces the next program, a 'program that computes a Y', and somehow glue them together into a 'program which computes a Y' " (which is the bind operation). The IO monad was born."


edit: These are the four types of generalized function application:

( $ ) ::                     (a ->   b) ->   a ->   b     -- plain
(<$>) :: Functor f     =>    (a ->   b) -> f a -> f b     -- functorial
(<*>) :: Applicative f =>  f (a ->   b) -> f a -> f b     -- applicative
(=<<) :: Monad f       =>    (a -> f b) -> f a -> f b     -- monadic

And here are the corresponding type derivation rules, with the flipped arguments order for clarity,

 a                f a                f  a                f a
 a -> b             a -> b           f (a -> b)            a -> f b
 ------           --------           ----------          ----------
      b           f      b           f       b           f        b

 no `f`s          one `f`            two `f`s,           two `f`s: 
                                     both known          one known,
                                                         one constructed

Why? They just are. Your question is really, why do we need Monads? Why Functors or Applicative Functors aren't enough? And this was surely already asked and answered many times (e.g., the 2nd link in the list just above). For one, as I tried to show above, monads let us code new computations in Haskell.

Will Ness
  • 62,652
  • 8
  • 86
  • 167