Functor solves the problem of mapping regular one-parameter functions into a sub-category, but that's not easy for functions with more than one parameters.
Let's consider a function with two parameters
f :: a -> b -> c, which can also read as
a -> (b -> c). Applying
f, we will get
fmap f :: m a -> m (b -> c). There's still some distance from what we want:
f' :: m a -> m b -> m c. To get
f', we need a transform from
m (b -> c) to
m b -> m c. Here we denote it as
<*> :: m （b -> c) -> m b -> m c. We will later show that such transform is universal for functions with more parameters.
Now consider a function with three parameters
f :: a -> b -> c -> d. We are going to transform it into a wrapped-value version, with the help of
f :: a -> b -> c -> d (fmap f) :: m a -> m (b -> (c -> d)) \a_ b_ -> (fmap f a_) <*> b_ :: m a -> m b -> m (c -> d) \a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_ :: m a -> m b -> m c -> (m d)
\a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_ is in the desired type. For most of the time, applying parameters directly is actually what we want, instead of the function itself, so the code could simply be written as
((fmap f a) <*> b) <*> c, where
c are wrapped values. Parenthesis could be omitted if precedences are set properly, which leads to a neat and easy-to-read form:
f `fmap` a <*> b <*> c
fmap has an infix name
<$>. So finally we get:
f <$> a <*> b <*> c.
Haskell pre-defined a type class
Applicative, which captures the pattern
<*>. Any type that implements
Applicative works well with
class Functor f => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
Note that an
Applicative is also a
Functor. Apart from
<*>, there are some other helper functions or operators in
pure is equivalent to the default value constructor of
Maybe. This may be handful when lifting an unwrapped value to a wrapped one.
liftA2 transforms a binary operator to the corresponding version. The function exists as binary operators would be frequently passed among high-order functions.
*> takes two wrapped parameters and simply returns the second one, which sequence up two wrapped values. This is quite useful for
Applicative with action semantics, such as
IO. In fact, it's so useful that Haskell introduces a syntax sugar for it, known as the
do putStrLn "1" putStrLn "2"
is equivalent to
putStrLn "1" *> putStrLn "2"
<* is similar. Both will be reviewed while studying Monad.