user6445533
user6445533

Reputation:

Why does bind of the function instance supply the original value to the next computation?

As a functional Javascript developer with only a vague understanding of Haskell I really have a hard time to understand Haskell idioms like monads. When I look at >>= of the function instance

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

instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r

// Javascript:

and its application with Javascript

const bind = f => g => x => g(f(x)) (x);

const inc = x => x + 1;

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);


f(2); // 4
f(5); // 15

the monadic function (a -> (r -> b)) (or (a -> m b)) provides a way to choose the next computation depending on the previous result. More generally, the monadic function along with its corresponding bind operator seems to give us the capability to define what function composition means in a specific computational context.

It is all the more surprising that the monadic function doesn't supply the result of the previous computation to the subsequent one. Instead, the original value is passed. I'd expect f(2)/f(5) to yield 6/18, similar to normal function composition. Is this behavior specific to functions as monads? What do I misunderstand?

Upvotes: 2

Views: 162

Answers (3)

danidiaz
danidiaz

Reputation: 27756

As already mentioned by chi, this line

const f = bind(inc) (x => x <= 5 ? x => x * 2 : x => x * 3);

would be clearer as something like

const f = bind(inc) (x => x <= 5 ? y => y * 2 : y => y * 3);

the Monad instance for functions is basically the Reader monad. You have a value x => x + 1 that depends on an enviroment (it adds 1 to the environment).

You also have a function which, depending on its input, returns one value that depends on an environment (y => y * 2) or another value that depends on an environment (y => y * 3).

In your bind, you are only using the result of x => x + 1 to choose between these two functions. Your are not returning the previous result directly. But you could, if you returned constant functions which ignored their environments and returned a fixed value depending only on the previous result:

const f = bind(inc) (x => x <= 5 ? _ => x * 2 : _ => x * 3);

(not sure about the syntax)

Upvotes: 1

Daniel Wagner
Daniel Wagner

Reputation: 152837

I think your confusion arises from using functions that are too simple. In particular, you write

const inc = x => x + 1;

whose type is a function that returns values in the same space as its input. Let's say inc is dealing with integers. Because both its input and output are integers, if you have another function foo that takes integers, it is easy to imagine using the output of inc as an input to foo.

The real world includes more exciting functions, though. Consider the function tree_of_depth that takes an integer and creates a tree of strings of that depth. (I won't try to implement it, because I don't know enough javascript to do a convincing job of it.) Now all of a sudden it's harder to imagine passing the output of tree_of_depth as an input to foo, since foo is expecting integers and tree_of_depth is producing trees, right? The only thing we can pass on to foo is the input to tree_of_depth, because that's the only integer we have lying around, even after running tree_of_depth.

Let's see how that manifests in the Haskell type signature for bind:

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

This says that (>>=) takes two arguments, each functions. The first function can be of any old type you like -- it can take a value of type r and produce a value of type a. In particular, you don't have to promise that r and a are the same at all. But once you pick its type, then the type of the next function argument to (>>=) is constrained: it has to be a function of two arguments whose types are the same r and a as before.

Now you can see why we have to pass the same value of type r to both of these functions: the first function produces an a, not an updated r, so we have no other value of type r to pass to the second function! Unlike your situation with inc, where the first function happened to also produce an r, we may be producing some other very different type.

This explains why bind has to be implemented the way it is, but maybe doesn't explain why this monad is a useful one. There is writing elsewhere on that. But the canonical use case is for configuration variables. Suppose at program start you parse a configuration file; then for the rest of the program, you want to be able to influence the behavior of various functions by looking at information from that configuration. In all cases it makes sense to use the same configuration information -- it doesn't need to change. Then this monad becomes useful: you can have an implicit configuration value, and the monad's bind operation makes sure that the two functions you're sequencing both have access to that information without having to manually pass it in to both functions.

P.S. You say

It is all the more surprising that the monadic function doesn't supply the result of the previous computation to the subsequent one.

which I find slightly imprecise: in fact in m >>= f, the function f gets both the result of m (as its first argument) and the original value (as its second argument).

Upvotes: 4

Alec
Alec

Reputation: 32309

More generally, the monadic function along with its corresponding bind operator seems to give us the capability to define what function composition means in a specific computational context.

I'm not sure what you mean by the "monadic function". Monads (which in Haskell consist of a bind function and a pure function) let you express how a series of monadic actions can be chained together ((<=<) is the monad equivalent of composition, equivalent to (.) for the Identity monad). In that sense, you do sort of get composition, but only composition of actions (functions of the form a -> m b).

(This is further abstracted in the Kleisli newtype around functions of the type a -> m b. Its category instance really lets you write the sequencing of monadic actions as composition.)

I'd expect f(2)/f(5) to yield 6/18, similar to normal function composition.

Then, you can just use normal function composition! Don't use a monad if you don't need one.

It is all the more surprising that the monadic function doesn't supply the result of the previous computation to the subsequent one. Instead, the original value is passed. ... Is this behavior specific to functions as monads?

Yes, it it. The monad Monad ((->) r) is also known as the "reader monad" because it only reads from its environment. That said, as far as monads are concerned, you are still passing the monadic result of the previous action to the subsequent one - but those results are themselves functions!

Upvotes: 1

Related Questions