Learn You a Haskell for Great Good! - Miran Lipovaca [104]
With normal functors, when you map a function over a functor, you can’t get the result out in any general way, even if the result is a partially applied function. Applicative functors, on the other hand, allow you to operate on several functors with a single function.
The Applicative Style
With the Applicative type class, we can chain the use of the <*> function, thus enabling us to seamlessly operate on several applicative values instead of just one. For instance, check this out:
ghci> pure (+) <*> Just 3 <*> Just 5
Just 8
ghci> pure (+) <*> Just 3 <*> Nothing
Nothing
ghci> pure (+) <*> Nothing <*> Just 5
Nothing
We wrapped the + function inside an applicative value and then used <*> to call it with two parameters, both applicative values.
Let’s take a look at how this happens, step by step. <*> is left-associative, which means that this:
pure (+) <*> Just 3 <*> Just 5
is the same as this:
(pure (+) <*> Just 3) <*> Just 5
First, the + function is put in an applicative value—in this case, a Maybe value that contains the function. So we have pure (+), which is Just (+). Next, Just (+) <*> Just 3 happens. The result of this is Just (3+). This is because of partial application. Only applying the + function to 3 results in a function that takes one parameter and adds 3 to it. Finally, Just (3+) <*> Just 5 is carried out, which results in a Just 8.
Isn’t this awesome? Applicative functors and the applicative style of pure f <*> x <*> y <*> ... allow us to take a function that expects parameters that aren’t applicative values and use that function to operate on several applicative values. The function can take as many parameters as we want, because it’s always partially applied step by step between occurrences of <*>.
This becomes even more handy and apparent if we consider the fact that pure f <*> x equals fmap f x. This is one of the applicative laws. We’ll take a closer look at the applicative laws later in the chapter, but let’s think about how it applies here. pure puts a value in a default context. If we just put a function in a default context and then extract and apply it to a value inside another applicative functor, that’s the same as just mapping that function over that applicative functor. Instead of writing pure f <*> x <*> y <*> ..., we can write fmap f x <*> y <*> .... This is why Control.Applicative exports a function called <$>, which is just fmap as an infix operator. Here’s how it’s defined:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x
Note
Remember that type variables are independent of parameter names or other value names. The f in the function declaration here is a type variable with a class constraint saying that any type constructor that replaces f should be in the Functor type class. The f in the function body denotes a function that we map over x. The fact that we used f to represent both of those doesn’t mean that they represent the same thing.
By using <$>, the applicative style really shines, because now if we want to apply a function f between three applicative values, we can write f <$> x <*> y <*> z. If the parameters were normal values rather than applicative functors, we would write f x y z.
Let’s take a closer look at how this works. Suppose we want to join the values Just "johntra" and Just "volta" into one String inside a Maybe functor. We can do this:
ghci> (++) <$> Just "johntra" <*> Just "volta"
Just "johntravolta"
Before we see how this happens, compare the preceding line with this:
ghci> (++) "johntra" "volta"
"johntravolta"
To use a normal function on applicative functors, just sprinkle some <$> and <*> about, and the function will operate on applicatives and return an applicative. How cool is that?
Back to our (++) <$> Just "johntra" <*> Just "volta": First (++), which has a type