A few posts back I learned that functors are types that can be mapped over. The idea is that if we have a function a -> b, we would like to be able to apply that to as in different contexts, such as a list of a, a Maybe a, or an IO action that results in an a. In the previous post we referred to these contexts as "boxes", so we could lift a function a -> b to work on a box of a and return a box of b.
-- "Functor f =>" just means that `f` refers to a functor type (or a type of box)
fmap :: Functor f => (a -> b) -> f a -> f b
ghci> fmap succ (Just 4)
Just 5
ghci> fmap (^2) (Just 4)
Just 16
ghci> fmap (++"!") getLine
Hello World
"Hello World!"
All these calls map single argument functions over functors, which is neat, but a bit limiting. What happens if we map a two (or more) argument function like +?
ghci> :t fmap (+) (Just 4)
fmap (+) (Just 4) :: Num a => Maybe (a -> a)
This gives us a +4 function in a Maybe context, so fmap (+) (Just 4) = Just (4+). But how do we pass the second argument to this boxed up function? We can’t use fmap again, because it’s signature takes an (a -> b), not a f (a -> b). But if f is not just a functor, but an applicative functor, then we have another option. Applicative functors still support fmap, but also add some other functions, the main one being:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
The bizarre-looking <*> function takes a boxed-up function a -> b and applies it to a boxed-up a, which is just what we need in this case.