Haskell学习-functor

原文地址:Haskell学习-functor

什么是Functor

functor 就是可以执行 map 操作的对象,functor 就像是附加了语义的表达式,可以用盒子进行比喻。functor 的定义可以这样理解:给出a映射到b的函数和装了a的盒子,结果会返回装了b的盒子。fmap 可以看作是一个接受一个 function 和一个 functor 的函数,它把 function 应用到 functor 的每一个元素(映射)。

-- Functor的定义
class Functor f where
    fmap :: (a -> b) -> f a -> f b


某个类型要能进行映射操作(map over),就必须继承Functor基类,并实现其中的fmap函数。我们来看一下几种默认的Functor形态:

  1. 列表list,非常好理解,操作列表我们一般使用map函数,它其实就是fmap针对列表的一个具体实例,在list中它们是等价的。

    -- 作为functor 的定义:
    instance Functor [] where
        fmap = map
    
    -- 实例
    fmap (*2) [1,2,3]
    > [2,4,6]
    
  2. Maybe,它是haskell中使用很广泛的数据类型,它有 Just 值Nothing 两种情况,分别用于表示成功和失败的情况。

    -- Maybe 的 functor 定义:
    instance Functor Maybe where
        fmap f (Just x) = Just (f x)
        fmap f Nothing = Nothing
    
    -- 实例
    fmap (*2) (Just 1)
    > Just 2
    
    fmap (*2) (Nothing)
    > Nothing
    
  3. IO,输入与输出,比如读取键盘输入,打印字符串等

    -- IO 的 Functor 定义
    instance Functor IO where
        fmap f action = do
              result <- action
              return (f result)
    
    -- 实例
    fmap ("hello! "++) getLine
    jeff -- 输入名字,打印时添加“hello”
    > "hello! jeff"
    

Functor的 (->) r 形态

(->) r 其实表示的是函数结合,也就是等价于 (.)

-- 下面两个定义是等价的,也就是 (->) r 形式下的 Functor 其实等价于 结合律
instance Functor ((->) r) where
    fmap f g = (\x -> f (g x))

instance Functor ((->) r) where
    fmap = (.)

-- 实例
fmap (*3) (+100) 1
> 303

(*3) . (+100)  $ 1
> 303

functor law

如果某个类型遵守这两个定律,那么它与其他Functor对于映射方面就具有相同的性质。

  1. fmap id = id
    如果我们对 functor 做 map id,那得到的新的 functor 应该要跟原来的一样

    fmap id (Just 3) 
    > Just 3
    id (Just 3) 
    > Just 3
    
  2. fmap (f . g) = fmap f . fmap g 也就是 functor 是能应用于函数结合的。

Applicative Functor

  为什么需要 Applicative Functor,什么情况下使用它。从Functor定义我们知道,fmap函数只能映射单个盒子,但假设需要映射两个三个,甚至是更多的盒子呢?或者是要处理返回值是函数的盒子呢?而这就是 Applicative Functor 要处理的情况。
  Applicative Functor 可以看作是Functor的增加版,从定义可知,它主要包括 pure<*> 两个函数。

-- Applicative Functor 定义
class (Functor f) => Applicative f where
    pure :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b
  • pure :: a -> f a 意思就是把普通值放到默认的context(语义)下。比如如果是list,那么它代表的就是[] ,如果是 Maybe,那么它就是 Just 值 / Nothing

  • <*> 接受一个装有函数的 functor 跟另一个 functor, 非常类似于 fmap,它就像加强版的 fmap。以applicative style 的方式来使用 applicative functors。像是 pure f <*> x <*> y <*> ... 这个函数可以吃任意多的参数。

    -- 与fmap类型的对比,可以看出函数 a -> b 被装进了盒子 f 中
    (<*>) :: f (a -> b) -> f a -> f b
    fmap :: (a -> b) -> f a -> f b
    
    -- <*> 是左结合的,因此以下两个表达式是相等的
    pure (+) <*> Just 3 <*> Just 5 
    (pure (+) <*> Just 3) <*> Just 5。
    
  • <$>applicative functor 中另一个很常用的符号,它其实就是中缀版的fmap。因为结合fmap写applicative functor更加方便。

    (<$>) :: (Functor f) => (a -> b) -> f a -> f b
    f <$> x = fmap f x
    -- 用<*>实现相同的功能
    pure f <*> x = fmap f x
    

接着看一下几个默认的 applicative functor,继承Applicative,必须实现 pure<*> 函数

  1. Maybe 类型

    -- Maybe 的 Applicative 定义:
    instance Applicative Maybe where
        pure = Just
        Nothing <*> _ = Nothing
        (Just f) <*> something = fmap f something
    
    -- 实例
    pure (+3) <*> Just 9
    > Just 12
    
    pure (+) <*> Just 3 <*> Just 5
    > Just 8
    
  2. 列表list 也是 applicative functor,从定义可以看出使用list的Applicative style完全可以实现 list comprehension 的功能。所以 Applicative style 对于 list 而言是取代某些类型的 list comprehension 的好方式。

    -- list 的定义
    instance Applicative [] where
        pure x = [x]
        fs <*> xs = [f x | f <- fs, x <- xs]
    
    -- 实例
    [(+3),(*2)] <*> [1,2]
    > [4,5,2,4]
    
    --下面表达式具有相同的功能
    (*) <$> [2,5,10] <*> [8,10,11] -- Applicative style
    [ x * y | x <- [2,5,10], y <- [8,10,11]] -- list comprehension
    > [16,20,22,40,50,55,80,100,110]
    
  3. IO ,下面的IO的实例,可以把 getLine 看做是一个去真实世界中拿取字串的盒子, 而 applicative functor 表达式会创造一个比较大的盒子,这个大盒子会派两个盒子去终端拿取字串,并把结果串接起来放进自己的盒子中。

    --IO 的 Applicative instance
    instance Applicative IO where
        pure = return
        a <*> b = do
            f <- a
            x <- b
            return (f x)
    
    -- 实例 将输入的两个字符串合并
    (++) <$> getLine <*> getLine
    aa
    bb
    > "aabb"
    

Applicative Functor 的 (->) r 形态

(->) r 形态定义

instance Applicative ((->) r) where
    pure x = (\_ -> x)
    f <*> g = \x -> f x (g x)
  • pure 将一个值包成 applicative functor 的时候,他产生的结果永远都会是那个值
  • 将两个 applicative functor 喂给 <*> 可以产生一个新的 applicative functor

接着综合使用上面的知识,来看一下实际应用applicative的几种方式。相比起functor,applicative functor要更强大和灵活。

-- 左结合形式, 第一项必须为含有函数的functor,右边全部为functor
pure (\x y z -> x+ y +z) <*> Just 3 <*> Just 4 <*> Just 5
> Just 12
[(+3),(*2)] <*> [1,2]
> [4,5,2,4]

-- fmap(<$>) 形式,第一项为普通函数,右边都为functor
(+) <$> Just 1 <*> Just 2
> Just 3
(\x y z -> x + y +z) <$> [1,2] <*> [2,3] <*> [4,5]
> [7,8,8,9,8,9,9,10]

-- (<$>) (->) r 形式,全部为普通函数,用单个参数调用执行
(\x y z -> [x,y,z]) <$> (3+) <*> (*100) <*> (`div`2) $ 2
> [5,200,1]

Applicative Functor 辅助函数

  1. liftA2
    只是applicative的套用函数而已,当然还有3个参数的版本 liftA3,而 liftA 则等价于 fmap

    -- 与applicative 的等价形式
    liftA2 f a b = f <$> a <*> b
    
    -- 以下表达式功能一致
    liftA2 (:) (Just 3) (Just [4])
    (:) <$> Just 3 <*> Just [4]
    pure (:) <*> Just 3 <*> Just [4]
    
    > Just [3,4]
    
  2. sequenceA
      当套用在函数上时,sequenceA 接受装有一堆函数的list,并回传一个回传list的函数。当我们有一串函数,想要将相同输入都喂给它们并查看结果的时候,sequenceA非常好用。
      当使用在 I/O action 上的时候,sequenceAsequence 是等价的。他接受一串 I/O action 并回传一个 I/O action,这个 I/O action 会计算 list 中的每一个 I/O action,并把结果放在一个 list 中

    -- 以下是两种实现sequenceA功能一致的函数
    sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
    sequenceA = foldr (liftA2 (:)) (pure [])
    
    sequenceA [Just 3, Just 2, Just 1]
    > Just [3,2,1]
    
    -- 将list组合成所有可能的组合
    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]]
    
    sequenceA [(>4),(<10),odd] 7
    map (\f -> f 7) [(>4),(<10),odd]
    > [True,True,True]
    
    -- and接受一串Bool,并在所有值都为True时才返回True
    and $ sequenceA [(>4),(<10),odd] 7
    and $ map (\f -> f 7) [(>4),(<10),odd]
    > True
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容