内容简介:示例:Haskell 的函数类型定义会出现 -> 符号,比如代码 1:这个 -> 其实也是函子,其定义在
class Functor f where fmap :: (a -> b) -> f a -> f b instance Functor IO where fmap f action = do result <- action return (f result) 复制代码
示例:
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 实际上就是函数组合。所以
instance Functor ((->) r) where fmap = (.) 复制代码
如果你想不通,可以把代码 1 的 f 改成 (->) r,你就得到了
fmap :: (a -> b) -> (->) r a -> (->) r b 复制代码
然后把 (->) r a 改成 r -> a,得到代码 2
fmap :: (a -> b) -> (r -> a) -> (r -> b) 复制代码
这个 fmap 接受 f1(a->b) 和 f2(r-a),得到一个新的函数 f3; f3 接受一个 r,通过 f2 把 r 变成 a,然后通过 f1 把 a 变成 b;这不就是函数组合么?
ghci> fmap (*3) (+100) 1 303 ghci> (*3) . (+100) $ 1 303 复制代码
问题在于,我现在无法把上面的代码与列表做对比了:
ghci> fmap (*2) [1.. 3] [2, 4, 6] 复制代码
列表很容易理解成容器,但是 (+100) 到底是什么容器呢?如果看代码 2 的话,说不定能理解一点:
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)一个函数。
两种方式思考 fmap
-
(a -> b) -> f a -> f b
接受函数(*2)
和函子值[1,2,3]
,在函子值上映射这个函数 -
(a -> b) -> (f a -> f b)
接受函数,把它提升为操作函子值的函数两种看法都对。
Applicative
先看定义
class (Functor f) => Applicative f where pure :: a -> f a (<*>) :: 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
的意思就很明显了:
- pure = Just 的意思是,如果你想把一个值装到 Maybe 里,用 Just 接受这个值即可
-
<*> Nothing _ = Nothing
是说,如果第一个盒子里没值,那么得到的盒子里一定也没值 -
<*> (Just f) something = fmap f something
是说,如果一个盒子里是 f,另一个盒子里不管是什么,<*>
的功能都是- 拿出 something 里的值
- 用 f 调用这个值
- 把值返回之前跟同类的盒子里这里我不太明白的地方在于,something 就没有什么约束吗?
Applicative Style
我们可以用 <*>
把一个函数和两个值连起来(但是要注意顺序,而且 <*>
是左结合的)
λ> :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 复制代码
列表也是 Applicative 的实例
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] 复制代码
IO 也是 Applicative 的实例
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 复制代码
(->) r 也是 Application 的实例
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 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript权威指南笔记
- Elasticsearch权威指南学习笔记
- 《Haskell趣学指南》笔记之模块
- 《Haskell趣学指南》笔记之函数
- redis 运维和开发指南-学习笔记
- 《Haskell趣学指南》笔记之高阶函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。