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.
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.
The Maybe monad represents computations which might "go wrong" by not returning a value.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With