首先咱们来从新思考一下 Functor 实例(函子),而后学习 Applicative数组
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor IO where  
fmap f action = do
result <- action
return (f result)
复制代码
示例:bash
main = do
line <- getLine
let line' = reverse line
putStrLn $ "You said " ++ line' ++ " backwards!"
-- 下面使用 fmap 改写上面的代码
main = do
line <- fmap reverse getLine
putStrLn $ "You said " ++ line ++ " backwards!"
-- 回顾一下列表的函子特性
ghci> fmap (*2) [1.. 3]
[2, 4, 6]
复制代码
Haskell 的函数类型定义会出现 -> 符号,好比代码 1:函数
class Functor f where
fmap :: (a -> b) -> f a -> f b
复制代码
这个 -> 其实也是函子,其定义在 Control.Monad.Instances
里:学习
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
复制代码
仔细观察就会发现,这个 fmap 实际上就是函数组合。因此spa
instance Functor ((->) r) where  
fmap = (.)
复制代码
若是你想不通,能够把代码 1 的 f 改为 (->) r,你就获得了code
fmap :: (a -> b) -> (->) r a -> (->) r b
复制代码
而后把 (->) r a 改为 r -> a,获得代码 2ci
fmap :: (a -> b) -> (r -> a) -> (r -> b)
复制代码
这个 fmap 接受 f1(a->b) 和 f2(r-a),获得一个新的函数 f3; f3 接受一个 r,经过 f2 把 r 变成 a,而后经过 f1 把 a 变成 b;这不就是函数组合么?get
ghci> fmap (*3) (+100) 1
303
ghci> (*3) . (+100) $ 1
303
复制代码
问题在于,我如今没法把上面的代码与列表作对比了:string
ghci> fmap (*2) [1.. 3]
[2, 4, 6]
复制代码
列表很容易理解成容器,可是 (+100) 究竟是什么容器呢?若是看代码 2 的话,说不定能理解一点:it
fmap :: (a -> b) -> (r -> a) -> (r -> b)
复制代码
(r ->) 就是一个容器吧。或者容器这个比喻在这里已经不适用了。
若是一个函数的类型是a -> b -> c
,就表示它接受一个 a 类型的值,返回一个 b -> c
函数。因此 a -> b -> c
等价于 a -> (b -> c)
。那么 fmap :: (a -> b) -> (f a -> f b)
能够写成这样,也就是说
fmap 接受 a -> b 类型的函数,返回 f a -> f b 类型的函数,其中 f 接受类型变量。具体化一下就是 (Int -> String) -> (Maybe Int -> Maybe String)
也就是说 fmap (*2) [1.. 3]
里的 (*2)
是 Int -> Int,通过 fmap 一折腾,变成了 [Int] -> [Int]。这种操做叫作提高(lifting)一个函数。
(a -> b) -> f a -> f b
接受函数(*2)
和函子值[1,2,3]
,在函子值上映射这个函数(a -> b) -> (f a -> f b)
接受函数,把它提高为操做函子值的函数两种见解都对。先看定义
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
复制代码
<*>
接受一个被装在 f 里的 (a ->b) 函数,再接受一个被装在 f 里的 a 值,返回一个装在 f 里的 b 值<*>
也可用另外一个角度思考,接受一个装在 f 里的 (a->b),返回一个 f a -> f b使用示例:
instance Applicative Maybe where  
pure = Just  
Nothing <*> _ = Nothing  
(Just f) <*> something = fmap f something
ghci> Just (+3) <*> Just 9
Just 12
ghci> pure (+3) <*> Just 9
Just 12
复制代码
为了方便对比,我把 Maybe 做为 Functor 实例的代码也复制过来
instance Functor Maybe where
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
复制代码
那么 instance Applicative Maybe
的意思就很明显了:
<*> Nothing _ = Nothing
是说,若是第一个盒子里没值,那么获得的盒子里必定也没值<*> (Just f) something = fmap f something
是说,若是一个盒子里是 f,另外一个盒子里无论是什么,<*>
的功能都是
咱们能够用 <*>
把一个函数和两个值连起来(可是要注意顺序,并且 <*>
是左结合的)
λ> :t pure (+) <*> Just 3
Just (+) <*> Just 3 :: Num a => Maybe (a -> a)
λ> pure (+) <*> Just 3 <*> Just 5
Just 8
λ> pure 3 <*> Just (+) <*> Just 5
error...
复制代码
为何要用 pure 开头呢?由于 pure 会把东西放在默认的上下文中(也就是 Just)。根据定义 (Just f) <*> something = fmap f something
pure (+) <*> Just 3
能够改写为 fmap (+) (Just 3)
所以 Control.Applicative 有一个 <$>
函数,实际上就是 fmap 的中缀版本:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x
复制代码
用上 <$>
以后,整个过程就更简洁了:若是想把 f 映射到两个 Application 实例的值上,能够写成
f <$> x <*> y
复制代码
若是想把 f 映射到两个普通值上,能够写成
f x y
复制代码
instance Applicative [] where  
pure x = [x]  
fs <*> xs = [f x | f <- fs, x <- xs]
ghci> [(*0),(+ 100),(^ 2)] <*> [1, 2, 3]
[0, 0, 0, 101, 102, 103, 1, 4, 9]
ghci> [(+),(*)] <*> [1, 2] <*> [3, 4]
[4, 5, 5, 6, 3, 4, 6, 8]
ghci> (++) <$> ["ha"," heh"," hmm"] <*> ["?","!","."]
["ha?"," ha!"," ha."," heh?"," heh!"," heh."," hmm?"," hmm!"," hmm."]
ghci> [ x* y | x <- [2, 5, 10], y <- [8, 10, 11]]
[16, 20, 22, 40, 50, 55, 80, 100, 110]
ghci> (*) <$> [2, 5, 10] <*> [8, 10, 11]
[16, 20, 22, 40, 50, 55, 80, 100, 110]
ghci> filter (>50) $ (*) <$> [2, 5, 10] <*> [8, 10, 11]
[55, 80, 100, 110]
复制代码
instance Applicative IO where  
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
myAction :: IO String
myAction = pure (++) <*> getLine <*> getLine -- 也能够写成 (**) <$> getLine <*> getLine
-- myAction 等价于
myAction :: IO String
myAction = do  
a <- getLine  
b <- getLine  
return $ a ++ b
复制代码
instance Applicative ((->) r) where  
pure x = (\_ -> x)  
f <*> g = \x -> f x (g x)
复制代码
说实话没看懂,可是示例看得懂:
ghci> :t (+) <$> (+3) <*> (*100)
(+) <$> (+3) <*> (*100) :: (Num a) => a -> a
ghci> (+) <$> (+3) <*> (*100) $ 5
508
复制代码
这个函数把+ 用在(+ 3) 和(* 100) 的结果上,而后返回。对于(+) <$> (+3) <*> (*100) $ 5
,(+ 3) 和(* 100) 先被应用到 5 上,返回 8 和 500,而后+ 以这两个值为参数被调用,返回 508。
ghci> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
[8. 0, 10. 0, 2. 5]
复制代码
ghci> liftA2 (:) (Just 3) (Just [4])
Just [3, 4]
ghci> (:) <$> Just 3 <*> Just [4]
Just [3, 4]
ghci> sequenceA [Just 3, Just 2, Just 1]
Just [3, 2, 1]
ghci> sequenceA [Just 3, Nothing, Just 1]
Nothing
复制代码