8

I'm trying to improve my understanding of Applicatives and Monads by implementing their function instances in Javascript. My knowledge of Haskell is limited and I hope that my question makes sense at all.

Here are my implementations of fmap, <*> and >>= for the Functor, Applicative and Monad typeclasses in Javascript:

const fmap = f => g => x => f(g(x)); // B combinator
const apply = f => g => x => f(x) (g(x)); // S combinator
const bind = f => g => x => g(f(x)) (x); // ?

I am not sure whether bind is the correct translation of the Haskell implementation:

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

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

Provided that bind is correct, how is it interpreted? I know that an Applicative can sequence effectful computations. I also know that a Monad in addition allows you to determine a next effect according to the result of a previous one.

I can see the sequences (eager evaluation order in Javascript):

  • apply: f(x) ... g(x) ... lambda(result of g) ... result of lambda
  • bind: f(x) ... g(result of f) ... lambda(x) ... result of lambda

However, the bind function looks pretty weird. Why are f and g nested the other way around? How is the specific Monad behavior (determines a next effect according to a previous one) reflected in this implementation? Actually g(f(x)) (x) looks like a function composition with flipped arguments, where g is a binary function.

When I apply apply/bind with an unary and a binary function, they yield the same result. This doesn't make much sense.

  • Have a look at [this example](http://stackoverflow.com/q/40026018/1048572) for a useful application of `bind` (known as `chain` in JS) – Bergi Oct 19 '16 at 13:47

2 Answers2

6

A few footnotes to Lee's answer:

However, the bind function looks pretty weird. Why are f and g nested the other way around?

Because bind is backwards. Compare (>>=) and its flipped version (=<<):

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

Or, in your specific example:

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

While in practice we tend to use (>>=) more often than (=<<) (because of how (>>=), syntactically speaking, lends itself well to the kind of pipeline monads are often used to build), from a theoretical point of view (=<<) is the most natural way of writing it. In particular, the parallels and differences with fmap/(<$>) and (<*>) are much more obvious:

(<$>) :: Functor f     =>   (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad f       => (a -> f b) -> f a -> f b

When I apply apply/bind with an unary and a binary function, they yield the same result. This doesn't make much sense.

That is an accidental fact about the function instances. Let's put the specialised signatures side by side:

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

Monad goes beyond Applicative by providing the means to determine the next effect according to previous results (as opposed to "previous effect" -- Applicative can do that already). The effect, in this case, consists of a function that generates values given an argument of type r. Now, since functions with multiple arguments (i.e. functions that return functions) can be flipped, it happens that there is no significant difference between (r -> (a -> b)) and (a -> (r -> b)) (flip can trivially change one into the other), which makes the Monad instance for (->) r entirely equivalent to the Applicative one.

duplode
  • 33,731
  • 7
  • 79
  • 150
  • 1
    @ftor You're welcome. Looking again at my answer, I noticed that I had overlooked a significant mix-up by reading your question too quickly. You said that the difference between `Monad` and `Applicative` is that with `Monad` you can determine "a next effect according to a previous one". `Applicative` already allows that, though. What `Monad` adds is determining effects from previous *results* (i.e. the underlying values in the functor, corresponding to the `a` in `Monad a => a -> m b`). – duplode Oct 19 '16 at 16:41
  • (in a recent controversial Q&A, I [appealed](https://stackoverflow.com/questions/67779824/does-the-function-monad-really-offer-something-more-than-the-function-applicativ/67780392?noredirect=1#comment119998278_67780392) to you as an authority, :) FYI) – Will Ness Jun 08 '21 at 15:48
  • @WillNess That was quite an animated discussion :) The point is indeed rather subtle; I had to reread the thread a couple times to get what the controversy was about. An extra point of view I'm tempted to consider is generalising from the function functor to `Distributive`s, and looking at how for e.g. infinite streams the diagonal join is (pardon the abuse of notation) `join u = flip (!) <$> [0..] <*> u`. I haven thought that through, though. – duplode Jun 10 '21 at 02:26
  • I still don't understand it. I see the second answer there as bunch of part vacuous / part irrelevant statements. I don't understand Distributive, but regarding the diagonal join, it figures, since it's isomorphic to ZipList's Applicative (which has no Monad instance). Looking at Distributive's docs, it says it's isomorphic to the functions. it also mentions zipping, and indeed `f x x ~~ ((f%) !! x) !! x` where `f%` is `f` seen as a list. (I'm more of a down-to-Earth Lisper myself, originally, with some upward aspirations). – Will Ness Jun 10 '21 at 07:32
  • @WillNess On Distributive: exactly. By the way, `flip (!) <$> [0..]` in the definition above is a counterpart to `flip ($)` in the function functor `join` in Enlico's question -- in particular, both fit the more general type `Distributive g => g (g a -> a)`. – duplode Jun 11 '21 at 01:27
4

The values in the monad instance for functions have type r -> a for some fixed type r. The function (a -> (r -> b)) given to (>>=) allows you to choose the next function to return given the result from the current value (a function r -> a). f r has type a and k (f r) has type r -> b which is the next function to apply.

In your code g(f(x)) is therefore a function which expects a single argument of type r. The caller of bind can choose this function based on the value returned by the previous function e.g.

var inc = x => x + 1;
var f = bind(inc)(function(i) {
   if(i <= 5) { return x => x * 2; }
   else { return x => x * 3; }
});

The function will be given x as an input and can choose the next stage in the computation based on the result of inc(x) e.g.

f(2) //4;
f(5) //15;
Lee
  • 142,018
  • 20
  • 234
  • 287
  • Thanks, it is so obvious now! When I try to transfer Haskell idioms to Javascript I often face the same problem: A lot of knowledge about Haskell idioms is hidden in their application, not just in their implementations or type signatures. –  Oct 19 '16 at 13:30
  • Now the next computation depends on the previous result, but it totally ignores this result. Is this the way `bind` usually works or just specific to your example? Sorry for all the questions! –  Nov 07 '16 at 16:44
  • @ftor - Sorry, the answer was wrong and your edit was the wrong fix. `bind` only uses `f(x)` to choose the next function but supplies the original input to it. – Lee Nov 07 '16 at 16:48