2

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?

  • The rule of thumb is: `bind(...)(x => ...)` takes the value of the previous computation and binds it to `x`. Other lambdas `x => ...` (not after a bind) instead access the same implicit read-only argument, the one passed to `f` at the very beginning. – chi Nov 07 '16 at 19:51
  • 3
    Side note: `x => x <= 5 ? x => x * 2 : x => x * 3` is unreadable, please use e.g. `x => x <= 5 ? y => y * 2 : z => z * 3` -- a computer does not care about alpha-equivalence, but humans do ;-) – chi Nov 07 '16 at 19:52
  • Because it *must* - that function is the only valid implementation of a function whose type is `forall r a b . (r -> a) -> (a -> (r -> b)) -> r -> b` (except undefined, of course). The javascript function can of course do whatever it likes in the abscence of formal types, but then reasoning about it would be much harder (and called it `bind` would just be wrong) – user2407038 Nov 07 '16 at 19:54
  • That is precisely how `>>=` differs from `.` in the function monad. With `f . g`, `f` doesn't get any information about what was passed to `g`, only what `g` produced. With `g >>= f`, `f` gets *both* the input to *and* the output from of `g`. – chepner Nov 07 '16 at 20:25
  • Possible duplicate of [How to use (->) instances of Monad and confusion about (->)](http://stackoverflow.com/questions/5310203/how-to-use-instances-of-monad-and-confusion-about) – Cactus Nov 08 '16 at 03:21
  • Note that if your second function *wasn't* getting the output of `inc`, it would actually return 10. You only get 15 because `inc` turns the 5 into a 6 before the second function does the `x <= 5` test, which triggers multiplication of the *other* parameter by 3 instead of 2. Your `f(5)` case is actually precisely what you needed to demonstrate that the behaviour is more complicated than *either* "both functions get the same input" or "the second gets the output of the first". – Ben Nov 08 '16 at 03:48
  • @Cactus Your link is indeed helpful, still I don't think my question is a dupe, because it is more specific and more importantly, it is a cross language. I know that the monad concept is language agnostic, but people are not. –  Nov 08 '16 at 19:32

3 Answers3

4

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).

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • Great, extensive response, Thanks! `r` and `a` can be of different type of course. And with the given type for `>>=`, no other valid implementation is possible. What I like about `(->)` instances of Monads, Applicatives and Functors is that that they are a familiar concept (combinators) applied within another concept, which is quite strange to me. They are a suitable starting point to understand this new concept. Programming to mathematical interfaces is hard, especially in Javascript where you can just make stuff up. I guess I just took another step forward. Thanks again! –  Nov 08 '16 at 17:39
  • Functions can be seen as a kind of type constructor, because they may transform an `r` in an `a` of different type. When `m a`/`m b` within `(>>=) :: m a -> (a -> m b) -> m b` are functions of type `(r -> a)`/`(r -> b)`, there is no alternative for `(r -> b)` to ignore the result `a` of the previous operation. Provided that `(r -> a)` is of type `(r -> r)` you can use the `K` combinator to pass the computed value to `(r -> b)`, because `K` is `return` for the function instance. Sorry if I should repeat myself, but I finally understood it. –  Nov 09 '16 at 09:23
1

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!

Alec
  • 31,829
  • 7
  • 67
  • 114
  • OK, I won't use monadic function anymore, but _action_ or _kleisli arrow_. Thanks! –  Nov 08 '16 at 17:47
1

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)

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • Thanks for your answer. I guess your last example is rather an unusual use of `bind`, right? –  Nov 08 '16 at 17:44
  • Not at all! Every `Monad` instance definition requires, besides `bind`, another function that puts a pure value in a "neutral" context. In Haskell that other function is (confusingly) named `return`. `return` for the function/Reader monads in just the constant function that receives the environment and ignores it. – danidiaz Nov 08 '16 at 19:31
  • 1
    ah, `return` of the `(->)` instance is just the `K` combinator. Got it! –  Nov 08 '16 at 19:34