7
votes

Let's say that we have two monadic functions:

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c

The bind function is defined as

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

My question is why can not we do something like below. Declare a function which would take a monadic value and returns another monadic value?

  f :: a -> m b
  g :: m b -> m c
  h :: a -> m c

The bind function is defined as

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

What is in the haskell that restricts a function from taking a monadic value as it's argument?

EDIT: I think I did not make my question clear. The point is, when you are composing functions using bind operator, why is that the second argument for bind operator is a function which takes non-monadic value (b)? Why can't it take a monadic value (mb) and give back mc . Is it that, when you are dealing with monads and the function you would compose will always have the following type.

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c

and h = f 'compose' g

I am trying to learn monads and this is something I am not able to understand.

9
There is nothing that restricts functions from doing that. They're just different, and less generally useful, functions.user395760
flip id has (among others) the type Monad m => m a -> (m a -> m b) -> m b.Daniel Wagner

9 Answers

7
votes

A key ability of Monad is to "look inside" the m a type and see an a; but a key restriction of Monad is that it must be possible for monads to be "inescapable," i.e., the Monad typeclass operations should not be sufficient to write a function of type Monad m => m a -> a. (>>=) :: Monad m => m a -> (a -> m b) -> m b gives you exactly this ability.

But there's more than one way to achieve that. The Monad class could be defined like this:

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

class Functor f => Monad m where
    return :: a -> m a
    join :: m (m a) -> m a

You ask why could we not have a Monad m => m a -> (m a -> m b) -> m b function. Well, given f :: a -> b, fmap f :: ma -> mb is basically that. But fmap by itself doesn't give you the ability to "look inside" a Monad m => m a yet not be able to escape from it. However join and fmap together give you that ability. (>>=) can be written generically with fmap and join:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)

In fact this is a common trick for defining a Monad instance when you're having trouble coming up with a definition for (>>=)—write the join function for your would-be monad, then use the generic definition of (>>=).


Well, that answers the "does it have to be the way it is" part of the question with a "no." But, why is it the way it is?

I can't speak for the designers of Haskell, but I like to think of it this way: in Haskell monadic programming, the basic building blocks are actions like these:

getLine :: IO String
putStrLn :: String -> IO ()

More generally, these basic building blocks have types that look like Monad m => m a, Monad m => a -> m b, Monad m => a -> b -> m c, ..., Monad m => a -> b -> ... -> m z. People informally call these actions. Monad m => m a is a no-argument action, Monad m => a -> m b is a one-argument action, and so on.

Well, (>>=) :: Monad m => m a -> (a -> m b) -> m b is basically the simplest function that "connects" two actions. getLine >>= putStrLn is the action that first executes getLine, and then executes putStrLn passing it the result that was obtained from executing getLine. If you had fmap and join and not >>= you'd have to write this:

join (fmap putStrLn getLine)

Even more generally, (>>=) embodies a notion much like a "pipeline" of actions, and as such is the more useful operator for using monads as a kind of programming language.


Final thing: make sure you are aware of the Control.Monad module. While return and (>>=) are the basic functions for monads, there's endless other more high-level functions that you can define using those two, and that module gathers a few dozen of the more common ones. Your code should not be forced into a straitjacket by (>>=); it's a crucial building block that's useful both on its own and as a component for larger building blocks.

5
votes

why can not we do something like below. Declare a function which would take a monadic value and returns another monadic value?

f :: a -> m b
g :: m b -> m c
h :: a -> m c

Am I to understand that you wish to write the following?

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose f g = h where
  h = ???

It turns out that this is just regular function composition, but with the arguments in the opposite order

(.) :: (y -> z) -> (x -> y) -> (x -> z)
(g . f) = \x -> g (f x)

Let's choose to specialize (.) with the types x = a, y = m b, and z = m c

(.) :: (m b -> m c) -> (a -> m b) -> (a -> m c)

Now flip the order of the inputs, and you get the desired compose function

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose = flip (.)

Notice that we haven't even mentioned monads anywhere here. This works perfectly well for any type constructor m, whether it is a monad or not.


Now let's consider your other question. Suppose we want to write the following:

composeM :: (a -> m b) -> (b -> m c) -> (a -> m c)

Stop. Hoogle time. Hoogling for that type signature, we find there is an exact match! It is >=> from Control.Monad, but notice that for this function, m must be a monad.

Now the question is why. What makes this composition different from the other one such that this one requires m to be a Monad, while the other does not? Well, the answer to that question lies at the heart of understanding what the Monad abstraction is all about, so I'll leave a more detailed answer to the various internet resources that speak about the subject. Suffice it to say that there is no way to write composeM without knowing something about m. Go ahead, try it. You just can't write it without some additional knowledge about what m is, and the additional knowledge necessary to write this function just happens to be that m has the structure of a Monad.

4
votes

Let me paraphrase your question a little bit:

why can't don't we use functions of type g :: m a -> m b with Monads?

The answer is, we do already, with Functors. There's nothing especially "monadic" about fmap f :: Functor m => m a -> m b where f :: a -> b. Monads are Functors; we get such functions just by using good old fmap:

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

If you have two functions f :: m a -> m b and a monadic value x :: m a, you can simply apply f x. You don't need any special monadic operator for that, just function application. But a function such as f can never "see" a value of type a.

Monadic composition of functions is much stronger concept and functions of type a -> m b are the core of monadic computations. If you have a monadic value x :: m a, you cannot "get into it" to retrieve some value of type a. But, if you have a function f :: a -> m b that operates on values of type a, you can compose the value with the function using >>= to get x >>= f :: m b. The point is, f "sees" a value of type a and can work with it (but it cannot return it, it can only return another monadic value). This is the benefit of >>= and each monad is required to provide its proper implementation.

To compare the two concepts:

  • If you have g :: m a -> m b, you can compose it with return to get g . return :: a -> m b (and then work with >>=), but
  • not vice versa. In general there is no way of creating a function of type m a -> m b from a function of type a -> m b.

So composing functions of types like a -> m b is a strictly stronger concept than composing functions of types like m a -> m b.


For example: The list monad represents computations that can give a variable number of answers, including 0 answers (you can view it as non-deterministic computations). The key elements of computing within list monad are functions of type a -> [b]. They take some input and produce a variable number of answers. Composition of these functions takes the results from the first one, applies the second function to each of the results, and merges it into a single list of all possible answers.

Functions of type [a] -> [b] would be different: They'd represent computations that take multiple inputs and produce multiple answers. They can be combined too, but we get something less strong than the original concept.


Perhaps even more distinctive example is the IO monad. If you call getChar :: IO Char and used only functions of type IO a -> IO b, you'd never be able to work with the character that was read. But >>= allows you to combine such a value with a function of type a -> IO b that can "see" the character and do something with it.

1
votes

As others have pointed out, there is nothing that restricts a function to take a monadic value as argument. The bind function itself takes one, but not the function that is given to bind.

I think you can make this understandable to yourself with the "Monad is a Container" metaphor. A good example for this is Maybe. While we know how to unwrap a value from the Maybe conatiner, we do not know it for every monad, and in some monads (like IO) it is entirely impossible. The idea is now that the Monad does this behind the scenes in a way you don't have to know about. For example, you indeed need to work with a value that was returned in the IO monad, but you cannot unwrap it, hence the function that does this needs to be in the IO monad itself.

1
votes

I like to think of a monad as a recipe for constructing a program with a specific context. The power that a monad provides is the ability to, at any stage within your constructed program, branch depending upon the previous value. The usual >>= function was chosen as being the most generally useful interface to this branching ability.

As an example, the Maybe monad provides a program that may fail at some stage (the context is the failure state). Consider this psuedo-Haskell example:

-- take a computation that produces an Int.  If the current Int is even, add 1.
incrIfEven :: Monad m => m Int -> m Int
incrIfEven anInt =
    let ourInt = currentStateOf anInt
    in if even ourInt then return (ourInt+1) else return ourInt

In order to branch based on the current result of a computation, we need to be able to access that current result. The above psuedo-code would work if we had access to currentStateOf :: m a -> a, but that isn't generally possible with monads. Instead we write our decision to branch as a function of type a -> m b. Since the a isn't in a monad in this function, we can treat it like a regular value, which is much easier to work with.

incrIfEvenReal :: Monad m => m Int -> m Int
incrIfEvenReal anInt = anInt >>= branch
  where branch ourInt = if even ourInt then return (ourInt+1) else return ourInt

So the type of >>= is really for ease of programming, but there are a few alternatives that are sometimes more useful. Notably the function Control.Monad.join, which when combined with fmap gives exactly the same power as >>= (either can be defined in terms of the other).

1
votes

The reason (>>=)'s second argument does not take a monad as input is because there is no need to bind such a function at all. Just apply it:

m :: m a
f :: a -> m b
g :: m b -> m c
h :: c -> m b

(g (m >>= f)) >>= h

You don't need (>>=) for g at all.

0
votes

The function can take a monadic value if it wants. But it is not forced to do so.

Consider the following contrived definitions, using the list monad and functions from Data.Char:

m :: [[Int]]
m = [[71,72,73], [107,106,105,104]]

f :: [Int] -> [Char]
f mx = do
    g <- [toUpper, id, toLower]
    x <- mx
    return (g $ chr x)

You can certainly run m >>= f; the result will have type [Char].

(It's important here that m :: [[Int]] and not m :: [Int]. >>= always "strips off" one monadic layer from its first argument. If you don't want that to happen, do f m instead of m >>= f.)

0
votes

As others have mentioned, nothing restricts such functions from being written.

There is, in fact, a large family of functions of type :: m a -> (m a -> m b) -> m b:

f :: Monad m => Int -> m a -> (m a -> m b) -> m b
f n m mf = replicateM_ n m >>= mf m

where

f 0 m mf = mf m

f 1 m mf = m >> mf m

f 2 m mf = m >> m >> mf m

... etc. ...

(Note the base case: when n is 0, it's simply normal functional application.)

But what does this function do? It performs a monadic action multiple times, finally throwing away all the results, and returning the application of mf to m.

Useful sometimes, but hardly generally useful, especially compared to >>=.

A quick Hoogle search doesn't turn up any results; perhaps a telling result.