Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
692 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...