欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

haskell - Functors, Applicative Functors and Monoids - Applicatives

程序员文章站 2024-01-29 19:32:40
...

 Applicative is a special form of functor, in our previoius post we have already discussed the functor with ((->) r); 

 

now, we will see the beefed up functor, which is in the Applicator typeclass,and you will import the Control.Applicative module.

 

and we will first examine how we can apply partially applied function to the Appilcative functors. so first let's see the use example of the functor as such 

 

 

ghci> :t fmap (++) (Just "hey")  
fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])  
ghci> :t fmap compare (Just 'a')  
fmap compare (Just 'a') :: Maybe (Char -> Ordering)  
ghci> :t fmap compare "A LIST OF CHARS"  
fmap compare "A LIST OF CHARS" :: [Char -> Ordering]  
ghci> :t fmap (\x y z -> x + y / z) [3,4,5,6]  
fmap (\x y z -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]  
 

 

so, what if we apply the functor mapping on the "multi-parameter" functions over functors?? first let 's see the example as below;. 

 

 

ghci> let a = fmap (*) [1,2,3,4]  
ghci> :t a  
a :: [Integer -> Integer]  
ghci> fmap (\f -> f 9) a  
[9,18,27,36]  
 

 

as we can see, when map (*) on the [1,2,3,4] (you can treat the [1,2,3,4] as the [] functor on the nmbers of 1,2,3,4 respectively); then you will get back a list of functors, and then you apply the function \f ->f to each of them. 

 

this is how the Applicative typeclass is defined, it is defined in the Control.Applicative module. 

 

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  
 to be an Applicative, you first has to be a Functor,and this has been manifested by the type constraint. the pure function servers as the "applicative functor instance" it means take value of any type and return an applicative functor with that value in it. 

 

 

the interesting part of the Applicative functor is the  <*> function. the type of the functor is f (a -> b) -> f a -> f b  ;it reminds us that it resembles that of (a -> b) -> f a -> f b, which is the type of fmap function. so it means it can take  a function and a funcotr and apply the function inside the functor... It basically performs a "do and run"...

 

so, if we were to implement the Applicative functor on Maybe type. here it is. 

 

instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something  
 

 

the reasoning of the Just is ignored. Let's see how we can apply that Just to the Maybe type. 

 

ghci> Just (+3) <*> Just 9  
Just 12  
ghci> pure (+3) <*> Just 10  
Just 13  
ghci> pure (+3) <*> Just 9  
Just 12  
ghci> Just (++"hahah") <*> Nothing  
Nothing  
ghci> Nothing <*> Just "woot"  
Nothing  
 

 

With normal functors, you can just map a function over a functor and then 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. Check out this piece of code:

 

 

ghci> pure (+) <*> Just 3 <*> Just 5  
Just 8  
ghci> pure (+) <*> Just 3 <*> Nothing  
Nothing  
ghci> pure (+) <*> Nothing <*> Just 5  
Nothing  
 

 

So, you have observed, the Applicative function gives that ability to chain something together. so as in "ability to operte on several functors with a single function.", <*> is left associative.

 

This can be even handy and apparent if we use the <$> notation, which has the folowing signatjure. 

 

(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x  
 it just takes the (fmap) function as the infix operator,it does a lifting, so that instead of writing pure f <*> x <*> y <*>  you can write fmap f x <*> y <*> ...and which can be even simplified as f <$> x <*> y <*> ...

 

 

here is the some examples that leveerage the <$> notation and other to performs some transformation. 

 

 

ghci> (++) <$> Just "johntra" <*> Just "volta"  
Just "johntravolta"  
 

 

so, in a  nutshell, the applicative function will take a applicative and returns an applicative. Hoiw cool is that.. 

 

Some times,you nee dto instruct to the just function to let it know the types. see below.

 

ghci> pure "Hey" :: [String]  
["Hey"]  
ghci> pure "Hey" :: Maybe String  
Just "Hey"  
 Let's take another look of the <*> on list ony. it should have a type of (<*>) :: [a -> b] -> [a] -> [b]. , and it is implemented as List comporehension. and what we will get is a list comprehension behavior, where every possible function from the left list to every possible value of the right list, the resulting list has every possible combination of applying a function from left list to a value in the right one. 

 

ghci> [(*0),(+100),(^2)] <*> [1,2,3]  
[0,0,0,101,102,103,1,4,9]  
 and yet another like this: 
ghci> [(+),(*)] <*> [1,2] <*> [3,4]  
[4,5,5,6,3,4,6,8]  
 we will compare that of a list comprehension to that of applicative ways .
so, instead of 
ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]]     
[16,20,22,40,50,55,80,100,110]     
 we can do this:
ghci> (*) <$> [2,5,10] <*> [8,10,11]  
[16,20,22,40,50,55,80,100,110]  
 and you can easily chain operation to the Applicative functors, such as 
ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]  
[55,80,100,110]  
 
IO can be made a instance of the Applicative functor, let's see how it is implemented. 
instance Applicative IO where  
    pure = return  
    a <*> b = do  
        f <- a  
        x <- b  
        return (f x)  
 
f <*> were specialized for IO it would have a type of(<*>) :: IO (a -> b) -> IO a -> IO b. It would take an I/O action that yields a function as its result and another I/O action and create a new I/O action from those two that, when performed, first performs the first one to get the function and then performs the second one to get the value and then it would yield that function applied to the value as its result. We used do syntax to implement it here. Remember, do syntax is about taking several I/O actions and gluing them into one, which is exactly what we do here.
However, in terms of the IO type, it is has more notion to come,  for one is so called notion of sequence, becasue we are taking  two I/O actions and we're sequencing, or gluing, them into one. We have to extract the function from the first I/O action, but to extract a result from an I/O action, it has to be performed.
suppose we are writing a function like below. 
myAction :: IO String  
myAction = do  
    a <- getLine  
    b <- getLine  
    return $ a ++ b  
 another way is writing as follow. 
myAction :: IO String  
myAction = (++) <$> getLine <*> getLine  
if we regress to the box analogy, we can image getLine as a box that will go out into the real world and fetch us a string. Doing (++) <$> getLine <*> getLine makes a new, bigger box that sends those two boxes out to fetch lines from the terminal and then presents the concatenation of those two lines as its result.
another instance of Applicate is the (->) , do  you still remember? 
the real Applicative implementation for the 
instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x)  
 let's see some exapmle of Appicate on the -> r
ghci> (pure 3) "blah"  
3  
this makes it even more hard to understand
 
ghci> pure 3 "blah"  
3  
a more real and obscure example is like this 
ghci> :t (+) <$> (+3) <*> (*100)  
(+) <$> (+3) <*> (*100) :: (Num a) => a -> a  
ghci> (+) <$> (+3) <*> (*100) $ 5  
508  
 
 If you want to have some one-2-one association from the functor and the argument that it shall applies to (some of the zipWith functions, you can try this) 
instance Applicative ZipList where  
        pure x = ZipList (repeat x)  
        ZipList fs <*> ZipList xs = ZipList (zipWith (\f x -> f x) fs xs)  
 
and you can use the getZipList to get a Show instance of ZipList, here is some of hte ZipList function in dry run. 
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]  
[101,102,103]  
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..]  
[101,102,103]  
ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]  
[5,3,3,4]  
ghci> getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat"  
[('d','c','r'),('o','a','a'),('g','t','t')]  
there is also an liftA2 function defined in the Control.Applicative module. the type of the liftA2 is liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
what it does is to lift a functio which takes two parameter (like most binary operator does) . so, it takes a normal binary function and promotes it to a function that operates on two functors.
 e.g of the use of liftA2 functor. 
ghci> liftA2 (:) (Just 3) (Just [4])  
Just [3,4]  
ghci> (:) <$> Just 3 <*> Just [4]  
Just [3,4]  
 
and to generalize something from the above, we can define some function is called sequenceA, and the definition is like this:
-- file
--  applicative_sequence.hs
-- description:
--   implement a sequence applicative 

import Control.Applicative

sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs  

-- no surprise, this is a recursive implementation of sequenceA

-- another way to implement this to use the foldr mehod 

sequenceA' :: (Applicative f) => [f a] -> f[a]
sequenceA' [] = pure []
sequenceA'  = foldr (liftA2 (:)) (pure [])
 
we can have some unexpected combination with the sequenceA impl, here are some 
ghci> map (\f -> f 7) [(>4),(<10),odd]  
[True,True,True]  
ghci> and $ map (\f -> f 7) [(>4),(<10),odd]  
True  
-- replaced with the sequenceA methods
ghci> sequenceA [(>4),(<10),odd] 7  
[True,True,True]  
ghci> and $ sequenceA [(>4),(<10),odd] 7  
True  
 with sequenceA to simulate the method call of the list comprehension .
ghci> sequenceA [[1,2,3],[4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
ghci> [[x,y] | x <- [1,2,3], y <- [4,5,6]]  
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]  
 and sequenceA on the IO actions 
ghci> sequenceA [getLine, getLine, getLine]  
heyh  
ho  
woo  
["heyh","ho","woo"]  
 
 
相关标签: haskell