内容简介:示例: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 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Machine Learning in Action
Peter Harrington / Manning Publications / 2012-4-19 / GBP 29.99
It's been said that data is the new "dirt"—the raw material from which and on which you build the structures of the modern world. And like dirt, data can seem like a limitless, undifferentiated mass. ......一起来看看 《Machine Learning in Action》 这本书的介绍吧!