《Haskell趣学指南》笔记之 Applicative 函子

栏目: 编程语言 · 发布时间: 7年前

内容简介:示例: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 的意思就很明显了:

  1. pure = Just 的意思是,如果你想把一个值装到 Maybe 里,用 Just 接受这个值即可
  2. <*> Nothing _ = Nothing 是说,如果第一个盒子里没值,那么得到的盒子里一定也没值
  3. <*> (Just f) something = fmap f something 是说,如果一个盒子里是 f,另一个盒子里不管是什么, <*> 的功能都是
    1. 拿出 something 里的值
    2. 用 f 调用这个值
    3. 把值返回之前跟同类的盒子里这里我不太明白的地方在于,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

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》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换