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"]
分享到:
相关推荐
第十一章讨论了Functors、Applicative Functors、Monoids,它们是构建复杂函数式程序的基础。通过理解Monoids和其在数据结构折叠(Folding)中的应用,读者可以学习到如何将数据结构聚合成单一值。 第十二章开始,...
本章重温了Functor的概念,并介绍了Applicative Functors和Monoids,这些都是Haskell中处理数据结构和组合函数的强大工具。通过使用Monoids,我们能高效地折叠(fold)数据结构。 ### 第十二章与第十三章:来看看几...
#### 十一、Functors, Applicative Functors与Monoids - **Functors**:介绍Functor类型类及其应用。 - **Applicative Functors**:讲解Applicative Functor类型类及其在函数式编程中的作用。 - **Monoids**:学习...
5. **Monoids**:Monoids是一类具有结合性和单位元的数据结构,例如整数加法或字符串连接。它们在FP中广泛用于聚合和组合操作。 6. **Monad Transformers**:Monad Transformer允许我们将不同类型的Monad组合在一起...