6

From Functor to Applicative

 2 years ago
source link: https://dannypsnl.github.io/blog/2020/04/11/cs/from-functor-to-applicative/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

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

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

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

<*> 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

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"]

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!


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK