Staring at ($), (< $ >), (< * >) and (>>=)

riccardoodone

Riccardo Odone

Posted on February 18, 2020

Staring at ($), (< $ >), (< * >) and (>>=)

You can keep reading here or jump to my blog to get the full experience, including the wonderful pink, blue and white palette.


Recently I've spent some time staring at type signatures. The goal was to develop a better intuition by absorbing their wisdom. Last week it was Monad's bind. This time I've decided to compare the following four:

($)   ::   (a ->   b) ->   a ->   b
(<$>) ::   (a ->   b) -> f a -> f b
(<*>) :: f (a ->   b) -> f a -> f b
(>>=) ::   (a -> m b) -> m a -> m b
Enter fullscreen mode Exit fullscreen mode

Function Application or ($)

($) :: (a -> b) -> a -> b
Enter fullscreen mode Exit fullscreen mode

It takes a funtion from a value of type a to a value of type b, an a and returns b. There's only one possible way to implement ($) which is to apply the funtion to the value of type a.

Functor's fmap or (<$>)

(<$>) :: (a -> b) -> f a -> f b
Enter fullscreen mode Exit fullscreen mode

The only difference from the previous is that a and b exist in a context f. For example, we could have an Int in a List context (i.e. [Int]), which means we went from one Int to any number of Ints. Or we could have an Int in a Maybe context (i.e. Maybe Int), in other words there could be either no Ints or just one Int. And so on and so forth depending on the semantics of each functor.

Again, it's easy to see how the value of type a must feed the function from a to b to generate the output. The only difference from ($) is that depending on the semantics of the context f, the function will be applied in a different way.

Applicative Functor's sequential application or (<*>)

(<*>) :: f (a -> b) -> f a -> f b
Enter fullscreen mode Exit fullscreen mode

In this instance, the function from a to b has a context f too. Therefore, the way the output is calculated depends on both the first and the second f (which must be the same f).

Monad's bind or (>>=)

(>>=) :: (a -> m b) -> m a -> m b
Enter fullscreen mode Exit fullscreen mode

This time, the way the function is applied depends only on the second m. This is the same situation as for (<$>). But there's one important change: the previous functions could only transform an a into a b. In the case of bind, the funtion decides not only on the b but also on the m, which must be the same m for both.

Concretely

Let's see the above in action in the context of Either which has an instance for Functor, Applicative Functor and Monad. Notice that the instances are defined for Either e because the context they provide is around one type, not two. For example, given an Int we can provide it an Either String context by doing Either String Int.

show 1
--> "1"



-- ($)

show $ 1
--> "1"



-- (<$>)

show <$> Right 1
--> Right "1"

show <$> Left "string"
--> Left "string"

-- Either maps the function only when the value is a `Right`.



-- (<*>)

Right show <*> Right 1
--> Right "1"

Right show <*> Left "string"
--> Left "string"

Left show <*> Right 1
--> Left show

Left show <*> Left "string"
--> Type error: the type on the left should be the same for both `Either`s.

-- Either applies the function only when both values are `Right`.



-- (>>=)

Right 1 >>= (\x -> Right (show x))
--> Right "1"

Left "string" >>= (\x -> Right (show x))
--> Left "string"

Right 1 >>= (\x -> Left (show x))
--> Left "1"

Left "string" >>= (\x -> Left (show x))
--> Left "string"

-- Either binds the function only when the value before `>>=` is a `Right`.
-- Contrarily to the previous cases, `>>=` can decide to return `Left` or `Right`.
Enter fullscreen mode Exit fullscreen mode

Get the latest content via email from me personally. Reply with your thoughts. Let's learn from each other. Subscribe to my PinkLetter!

💖 💪 🙅 🚩
riccardoodone
Riccardo Odone

Posted on February 18, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related