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
.
1. 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:
liftMaybe_n
problem, how to handleliftMaybe
for alln
.liftM
problem, how to handlelift
for differentM
.
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.
2. 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.
2.1. 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"]
3. 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!