DEV Community

林子篆
林子篆

Posted on • Originally published at dannypsnl.github.io

3

From Functor to Applicative

Last time we introduce Functor, a Functor is a container which provide a function can help another function operating the Functor. This function has a name fmap in Haskell. Therefore, a function take a type a as parameter(a -> b) can be lifted by fmap to handle M a, if M provided a fmap. For example, Maybe is a Functor, (+1) has the type Int -> Int, fmap (+1) (Just 10) get a result: Just 11.

Limitation of Functor

Oh, Functor seems so powerful, but programming is simple, life is hard! In the real world, a common situation is there has many M have to handle. For example:

replicateMaybe :: Maybe Int -> Maybe a -> Maybe [a]
replicateMaybe (Just len) (Just a) = Just $ replicate n a
replicateMaybe _ Nothing = Nothing
replicateMaybe Nothing _ = Nothing
Enter fullscreen mode Exit fullscreen mode

Can see that we fall back to pattern matching, line 3 and 4 exclude no input. We can make it easier by extract out this pattern:

liftMaybe2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
liftMaybe2 f (Just a) (Just b) = Just $ f a b
liftMaybe2 _ _ _ = Nothing
Enter fullscreen mode Exit fullscreen mode

Now liftMaybe2 repliacte a b can work just as expected. Sounds great? How about lift a -> b -> c -> d to M a -> M b -> M c -> M d. How about make a lift to another M, e.g. List? liftList? It seems like boilerplate code, right?

Now we have two problems:

  1. liftMaybe_n problem, how to handle liftMaybe for all n.
  2. liftM problem, how to handle lift for different M.

Indeed, let's dig into fmap again. Every function with type a -> b become M a -> M b, therefore, a -> b -> c would be M a -> M (b -> c). The key point is how to make M (b -> c) applied b.

applyMaybe :: Maybe (a -> b) -> Maybe a -> Maybe b
applyMaybe (Just f) (Just a) = Just $ f a
applyMaybe _ _ = Nothing
Enter fullscreen mode Exit fullscreen mode

Now take a look at how magic happened:

sum :: Int -> Int -> Int -> Int
sum a b c = a + b + c

(fmap sum $ Just 1) `applyMaybe` Just 2 `applyMaybe` Just 3
-- Just 6
Enter fullscreen mode Exit fullscreen mode

We solve liftMaybe_n problem! The only problem is it only works for Maybe, to solve the problem, it's the time of class.

Applicative can help!

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
Enter fullscreen mode Exit fullscreen mode

<*> is the general version of applyMaybe. pure could raise a variable into the calculation in Applicative, we also call this minimum context.

Special helper <$>

<$> has definition as below:

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

It just an alias of fmap to help infix syntax:

(+) <$> Just 1 <*> Just 2
-- Just 3
replicate <$> Just 3 <*> Just 'x'
-- Just "xxx"
replicate <$> [1, 2, 3] <*> ['x', 'y', 'z']
-- ["x", "y", "z", "xx", "yy", "zz", "xxx", "yyy", "zzz"]
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this article really help you understand why we need Applicative. Next time would Monad or monoid, thanks for your read and have a good day!

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs