「学习笔记-Haskell」Chapter 11 Applicative Functions
Chapter 11 Applicative FunctionsTable of Contents1 Functors Redux1.1 I/O Actions As Functors1.2 Functions As Functors2 Functor Laws2.1 Law 12.2 Law 22.3 Breaking the Law3 Using Applicative Functors3.1 Say Hello to Applicative3.2 Maybe the Applicative Functor3.3 The Applicative Style3.4 Lists3.5 IO Is An Applicative Functor,Too3.6 Functions As Applicative3.7 Zip Lists3.8 Applicative Laws3.8.1 pure id <*> v = v3.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) <*> u4 Useful Functions for Applicatives
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.Charmain = do line <- fmap (reverse . map toUpper) getLine putStrLn $ "You said " ++ line ++ " backwards!"
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 inControl.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) $ 2306ghci>(*3) `fmap` (+100) $ 2306ghci>fmap (*3) (+100) 2306
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.
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.
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 4ghci>fmap id (Just 4)Just 4
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
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 CNothingghci>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.
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) aJust 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) )
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
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 4Just 7ghci>pure (3+) <*> Just 4Just 7
You see how doing pure (+3) and Just (+3) is the same in this case.
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 5Just 8ghci>pure (+) <*> Just 3 <*> NothingNothingghci>pure (+) <*> Nothing <*> Just 5Nothing
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 bf <$> 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"
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 StringJust "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~"]
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 StringmyAction = do a <- getLine b <- getLine return $ a ++ b
Another way of writing this is to use the applicative style.
myAction :: IO StringmyAction = (++) <$> getLine <*> getLine
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"3ghci>(+) <$> (+3) <*> (*100) $ 4407ghci>(*) <$> (+3) <*> (*100) $ 42800
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]
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)
Like normal functors, applicative functors come with a few laws.
Control.Applicative defines a function that’s called liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f cliftA2 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]Nothingghci>sequnceA [(+1), (*2), (^3)] 3[4,6,27]