「学习札记-Haskell」Chapter 11 Applicative Functions
Chapter 11 Applicative Functions
Table of Contents
- 1 Functors Redux
- 1.1 I/O Actions As Functors
- 1.2 Functions As Functors
- 2 Functor Laws
- 2.1 Law 1
- 2.2 Law 2
- 2.3 Breaking the Law
- 3 Using Applicative Functors
- 3.1 Say Hello to Applicative
- 3.2 Maybe the Applicative Functor
- 3.3 The Applicative Style
- 3.4 Lists
- 3.5 IO Is An Applicative Functor,Too
- 3.6 Functions As Applicative
- 3.7 Zip Lists
- 3.8 Applicative Laws
- 3.8.1 pure id <*> v = v
- 3.8.2 pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
- 3.8.3 pure f <*> pure x = pure (f x)
- 3.8.4 u <*> pure y = pure ($ y) <*> u
- 4 Useful Functions for Applicatives
1 Functors Redux
1.1 I/O Actions As Functors
Let’s see how IO is an instance of Functor. When we fmap a function over an I/O action, we want to get back an I/O action that does the same thing but has our function applied over its result value. Here’s the code:
instance Functor IO where fmap f action = do result <- action return (f result)
Check out this piece of code
main = do line <- getLine let line' = reverse line putStrLn $ "You said " ++ line' ++ " backwards!" putStrLn $ "Yes, you said " ++ line' ++ " backwards!"
We can rewrite it use fmap.
main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwards!" putStrLn $ "Yes, you said " ++ line ++ " backwards!"
getLine is an I/O action that has a type of IO String, and mapping reverse over it gives us an I/O action that will go out into the real world and get a line and then apply reverse to its result.
We can also fmap multi functions to I/O action.
import Data.Char main = do line <- fmap (reverse . map toUpper) getLine putStrLn $ "You said " ++ line ++ " backwards!"
1.2 Functions As Functors
Another instance of Functor that we’ve been dealing with all along is (->) r. If you understand the that r -> a can be write as (->) r a, you may understand what is (->) r.
How are functions functors? Let’s take a look at the implementation, which lies in Control.Monad.Instances.
instance Functor ((->) r) where fmap f g = (\x -> f (g x))
First, let’s think about fmap’s type:
fmap :: (a -> b) -> f a -> f b
Next, let’s mentally replace each f, which is the role that our functor instance plays, with (->) r.
fmap :: (a -> b) -> ((->) r a) -> ((->) r b)
Then, we change prefix to infix.
fmap :: (a -> b) -> (r -> a) -> (r -> b)
We see that it takes a function from a to b and a function from r to a and returns a function from r to b. Does this remind you of anything? Yes, function composition!
Here’s another way to write this instance.
instance Functor ((->) r) where fmap = (.)
This makes it clear that using fmap over functions is just function composition. Let's see several examples.
ghci>(*3) . (+100) $ 2 306 ghci>(*3) `fmap` (+100) $ 2 306 ghci>fmap (*3) (+100) 2 306
Note that you should import Control.Monad.Instances first.
Using fmap (*3) on (+100) will create another function that acts like (+100), but before producing a result, (*3) will be applied to that result.Think about the similarity between this process with functors on IO action.
2 Functor Laws
All functors are expected to exhibit certain kinds of properties and behav- iors. They should reliably behave as things that can be mapped over. This behavior is described in the functor laws. All instances of Functor should abide by these two laws.
2.1 Law 1
The first functor law states that if we map the id function over a functor value, the functor value that we get back should be the same as the original functor value.
ghci>id (Just 4) Just 4 ghci>fmap id (Just 4) Just 4
2.2 Law 2
The second law says that composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other one.
fmap (f . g) = fmap f . fmap g
2.3 Breaking the Law
Let’s take a look at a pathological example of a type constructor being an instance of the Functor type class but not really being a functor, because it doesn’t satisfy the laws.
data CMaybe a = CNothing | CJust Int a deriving (Show)
Let’s play with our new type
ghci>CNothing CNothing ghci>CJust 1 "hiahia" CJust 1 "hiahia"
Let’s make this an instance of Functor so that each time we use fmap, the func- tion is applied to the second field, whereas the first field is increased by 1
data CMaybe a = CNothing | CJust Int a deriving (Show) instance Functor CMaybe where fmap f CNothing = CNothing fmap f (CJust counter x) = CJust (counter+1) (f x)
ghci>fmap (++"hahia") (CJust 0 "ho") CJust 1 "hohahia" ghci>fmap (++"xxx") $ fmap (++"hahia") (CJust 0 "ho") CJust 2 "hohahiaxxx"
Does this obey the functor laws?We can find a counterexample easily.
ghci>id (CJust 0 "hia") CJust 0 "hia" ghci>fmap id (CJust 0 "hia") CJust 1 "hia"
Since CMaybe fails at being a functor even though it pretends to be one, using it as a functor might lead to some faulty code.
The next time you make a type an instance of Functor, take a minute to make sure that it obeys the functor laws.
3 Using Applicative Functors
So far, we have focused on mapping functions that take only one parameter over functors. But what happens when we map a function that takes two parameters over a functor?
ghci>:t fmap (*) (Just 3) fmap (*) (Just 3) :: Num a => Maybe (a -> a)
We see how by mapping “multiparameter” functions over functor val- ues, we get functor values that contain functions inside them. So now what can we do with them?
ghci>let a = fmap (*) (Just 3) ghci>fmap (\f -> f 4) a Just 12
You can think of this process like.
a = fmap (*) (Just 3) -> a = (Just (*) 3) -> a = (Just (3 *) ) fmap (\f -> f 4) a = fmap (\f -> f 4) (Just (3 *) ) = (Just (\f -> f 4) (3 *) ) = (Just ((3 *) 4) ) = (Just (3 * 4) )
3.1 Say Hello to Applicative
It doesn’t provide a default implementation for either of them, so we need to define them both if we want something to be an applicative functor. The class is defined like so
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b
3.2 Maybe the Applicative Functor
Let’s take a look at the Applicative instance implementation for Maybe.
instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
Now let’s give some examples:
ghci>Just (3+) <*> Just 4 Just 7 ghci>pure (3+) <*> Just 4 Just 7
You see how doing pure (+3) and Just (+3) is the same in this case.
3.3 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.
ghci>pure (+) <*> Just 3 <*> Just 5 Just 8 ghci>pure (+) <*> Just 3 <*> Nothing Nothing ghci>pure (+) <*> Nothing <*> Just 5 Nothing
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
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 ap- plicative functors, we would write f x y z.
ghci>(++) <$> Just "sdf" <*> Just "ewrq" Just "sdfewrq"
3.4 Lists
Lists (actually the list type constructor, []) are applicative functors.
instance Applicative [] where pure x = [x] fs <*> xs = [f x | f <- fs, x<-xs]
Let's look at some example to see what's the meaning of the definition above.
ghci>pure "Hia" :: [String] ["Hia"] ghci>pure "Hia" :: Maybe String Just "Hia"
ghci>[(*0),(+1),(^2)] <*> [1,2,3] [0,0,0,2,3,4,1,4,9]
In the following example, we apply two function between two lists:
ghci>[(*),(+)] <*> [1,2] <*> [3,4] [3,4,6,8,4,5,5,6]
Using the applicative style with lists is fun!
ghci>(++) <$> ["a","b","c"] <*> ["?","!","~"] ["a?","a!","a~","b?","b!","b~","c?","c!","c~"]
3.5 IO Is An Applicative Functor,Too
Another instance of Applicative that we’ve already encountered is IO.
instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (f x)
Consider this:
myAction :: IO String myAction = do a <- getLine b <- getLine return $ a ++ b
Another way of writing this is to use the applicative style.
myAction :: IO String myAction = (++) <$> getLine <*> getLine
3.6 Functions As Applicative
Another instance of Applicative is (->) r, or functions.
instance Applicative ((->) r) where pure x = (\_ -> x) f <*> g = (\x -> f x ( g x))
Example:
ghci>(pure 3) "sdf"
3
ghci>(+) <$> (+3) <*> (*100) $ 4
407
ghci>(*) <$> (+3) <*> (*100) $ 4
2800
When we do (+) <$> (+3) <*> (*100), we’re making a function that will use + on the results of (3) and (*100) and return that. With () <$> (+3) <*> (*100) $ 5, (+3) and (*100) are first applied to 5, resulting in 8 and 500. Then + is called with
8 and 500, resulting in 508. The following example is similar.
ghci>(\x y z -> [x,y,z]) <$> (+3) <*> (*4) <*> (^2) $ 5 [8,20,25]
3.7 Zip Lists
An instance of Applicative that we haven’t encountered yet is ZipList, and it lives in Control.Applicative.
instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)
3.8 Applicative Laws
Like normal functors, applicative functors come with a few laws.
pure id <*> v = v
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
pure f <*> pure x = pure (f x)
u <*> pure y = pure ($ y) <*> u
4 Useful Functions for Applicatives
Control.Applicative defines a function that’s called liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c liftA2 f a b = f <$> a <*> b
With ordinary functors, we can just map functions over one functor value. With applicative functors, we can apply a function between several functor values.
ghci>liftA2 (:) (Just 3) (Just [4]) Just [3,4]
Let’s try implementing a function that takes a list of applicative values and returns an applicative value that has a list as its result value. We’ll call it sequenceA.
sequnceA :: (Applicative f) => [f a] -> f [a] sequnceA [] = pure [] sequnceA (x:xs) = (:) <$> x <*> sequnceA xs
Test it!
ghci>sequnceA [Just 1, Just 2, Just 3] Just [1,2,3] ghci>sequnceA [Just 1, Nothing, Just 3] Nothing ghci>sequnceA [(+1), (*2), (^3)] 3 [4,6,27]