Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why can't a function take monadic value and return another monadic value?

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.

like image 254
polapts Avatar asked Aug 15 '12 10:08

polapts


People also ask

Is Monad an option?

Options are monads, so we can use flatMap (and therefore map) with them: However, if we try to map on a None , we get None : This is because Options are success-biased. That means, if map successfully finds a value in the Option , it executes the map function.

What is maybe Monad?

The Maybe monad represents computations which might "go wrong" by not returning a value.


2 Answers

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.

like image 137
Luis Casillas Avatar answered Sep 28 '22 08:09

Luis Casillas


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.

like image 39
Dan Burton Avatar answered Sep 28 '22 08:09

Dan Burton