首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

「学习札记-Haskell」Chapter 11 Applicative Functions

2013-01-28 
「学习笔记-Haskell」Chapter 11 Applicative FunctionsChapter 11 Applicative FunctionsTable of Contents

「学习笔记-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]

热点排行