Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the bind operator (>>=) defined as it is?

I have been studying Haskell for several weeks now (just for fun) and just watched Brian Beckman's great video introducing monads. He motivates monads with the need to create a more general composition operator. Following this line of thought, if I have two functions:

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

the composition operator should satisfy

h = g . f :: a -> c

and from this I can infer the correct type of the . operator:

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

When it comes to monads, suppose I have two functions:

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

It seems to me that the natural choice would have been to define a generalized composition operator that works as follows:

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

in which case the >>= operator would have a type signature of:

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

But actually the operator seems to be defined so that

h a = (f a) >>= g :: m c

and thus

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

Could someone explain the reasoning behind this choice of definition for bind? I assume there is some simple connection between the two choices where one can be expressed in terms of the other, but I'm not seeing it at the moment.

like image 685
broken.eggshell Avatar asked Jun 10 '20 16:06

broken.eggshell


People also ask

Why are there monads in Haskell?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What does the bind operator do Haskell?

Bind takes a non-composable function f and returns a new function g that accepts the monadic type as input and returns the monadic type. g is composable. The unit function takes an argument of the type that f expected, and wraps it in the monadic type.

What is a Monad example?

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it. For example, you can create a type to wrap another one, in Haskell: data Wrapped a = Wrap a. To wrap stuff we define return :: a -> Wrapped a return x = Wrap x.


3 Answers

Could someone explain the reasoning behind this choice of definition for bind?

Sure, and it's almost exactly the same reasoning you have. It's just... we wanted a more general application operator, not a more general composition operator. If you've done much (any) point-free programming, you'll immediately recognize why: point-free programs are hard to write, and incredibly hard to read, compared to pointful ones. For example:

h x y = f (g x y)

With function application, this is completely straightforward. What's the version that only uses function composition look like?

h = (f .) . g

If you don't have to stop and stare for a minute or two the first time you see this, you might actually be a computer.

So, for whatever reason: our brains are wired to work a bit better with names and function application out of the box. So here's what the rest of your argument looks like, but with application in place of composition. If I have a function and an argument:

f :: a -> b
x :: a

the application operator should satisfy

h = x & f :: b

and from this I can infer the correct type of the & operator:

(&) :: a -> (a -> b) -> b

When it comes to monads, suppose my function and argument are monadic:

f :: a -> m b
x :: m a

The natural choice is to define a generalized application operator that works as follows:

h = x >>= f :: m b

in which case the >>= operator would have a type signature of:

(>>=) :: m a -> (a -> m b) -> m b
like image 176
Daniel Wagner Avatar answered Oct 24 '22 22:10

Daniel Wagner


You can search for your operator on Hoogle, and see that it is called (>=>). Its definition in terms of (>>=) is quite simple:

f >=> g = \x -> f x >>= g

In some sense (>=>) is better reflective of the idea to generalize composition, but I think (>>=) works better as a primitive operator simply because it's practical in more cases, and easier to relate to do-notation.

like image 24
amalloy Avatar answered Oct 24 '22 20:10

amalloy


(>>=) is not a composition operator. It's an application operator.

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

There's also (=<<) (from Control.Monad), which corresponds to the more usual application operator ($):

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

For composition, we have both (<=<) and (>=>) (again from Control.Monad, the first being exactly analogous to (.):

(.)   ::            (b ->   c) -> (a ->   b) -> a ->   c
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c

((>=>) is just (<=<) with its arguments flipped; (>=>) = flip (<=<))


While we are comparing types, you might want to look at how fmap fits in.

($)   ::              (a ->   b) ->   a ->   b
fmap  :: Functor f => (a ->   b) -> f a -> f b
(=<<) :: Monad m   => (a -> m b) -> m a -> m b

($) and fmap take the same type of function, but apply it to different types of argument.

fmap and (=<<) take different types of functions, but apply them both to the same type of argument (though in different ways).

like image 23
chepner Avatar answered Oct 24 '22 20:10

chepner