ReactiveCocoa 与函数式编程有什么关系?

前言

ReactiveCocoa是一个FRP的思想在Objective-C中的实现框架。FRP即Functional Reactive Programming。
ReactiveCocoa 是基于函数式编程中一个叫Monad概念。这篇文章主要讲什么是Monad。
文中的代码以Haskell演示。
提及Monad通常会涉及到Functor, Applicative 他们三个经常一起出现,我们将依此讲解。

函数式编程中的一些概念背景知识

Typeclass

Typeclass 类似于Java中的接口,OC中的协议。
Typeclass 中定义了一些函数,实现一个typeclass就是要实现这些函数。
所有实现了这个typeclass的数据类型都拥有这些共同的行为。
定义Typeclass

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    x == y = not (x /= y)
    x /= y = not (x == y)

定义一个叫做Eq的typeclass。 a 一个型别变量,他代表a是任何我们在定义instance时的型别。然后又定义了4个函数。

Maybe

  1. Maybe类型封装了一个可选值,一个 Maybe a 类型的值要么包含一个a 类型的值(Just a),要么为空(Nothing表示)。
  2. Maybe看作一个上下文,这个上下文表示某次计算可能成功,也可能失败,成功是用(Just a)表示,失败时用Nothing表示。

提出问题

  1. 假设有一个值2, 如何将函数(+3) 应用到这个值上?
    幼儿园加法,大家都知道吧。
  2. 如果值2是一个上下文值呢?
    那就需要使用Functor。

Functor typeclass

//1. Functor typeclass
class Functor f where
  fmap :: (a->b)->f a -> f b
 
//2. Maybe Functor (即假设f 是Maybe)
class Functor Maybe where
  fmap :: (a ->b) -> Maybe a -> Maybe b
 
//3. Maybe Functor 实现
instance Functor Maybe where
  fmap func (Just x) = Just (func x)
  fmap func Nothing = Nothing
 
//4. Haskell 运行效果
*Main> fmap (+3) Nothing 
Nothing
*Main> fmap (+3) (Just 2)
Just 5

注释: 方法fmap有两个参数:
参数一:(a->b) 表示一个函数,函数的需要一个参数,输入运行结果b
参数二:f a 其中f是参数a的类型限制。比如:假如f 是Maybe, 则a 要么是Nothing, 要么是Just a
在 3 instance 中运用了模式匹配,当参数为Just x 时调用第一行。参数为Nothing时调用第二行。

问题又来了

  1. 如果函数(+3) 也在上下文中呢? 比如Maybe,此时(+3) 就变成了Just(+3)
    那就需要Applicative。

Applicative typeclass

//1. Applicative typeclass
class Functor f => Applicative f where
    pure :: a - > f a
    (<*>) :: f (a -> b) -> f a -> f b
 
//2. Maybe Applicative
class Functor Maybe => Applicative Maybe where
    pure :: a -> Maybe a
    (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
 
//3. Maybe Applicative 实现
instance Applicative Maybe where
    pure :: Just
    Nothing <*> _ = Nothing
    (Just func) <*> something = fmap func something
 
//4. Haskell 运行效果
*Main> (<*>) (Just (+3)) (Just 2)
Just 5
*Main> (<*>) (Just (+3)) Nothing 
Nothing

注释:=> 符号解释: =>左边的部分叫型别约束。即 Applicative 中 f 的型别是Functor

总结一下

Fouctor: 应用一个函数到一个上下文中的值,返回一个上下文中的值。
Applicative: 应用一个上下文中函数到一个上下文中的值,返回一个上下文中的值。

那么Monad是干什么呢

Monad:应用一个函数到一个上下文的值。并返回一个在上下文的值的函数。

//1. Monad classtype
class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> m b -> m b
 
//2. Maybe Monad
class Applicative Maybe => Monad Maybe where
    return :: a -> Maybe a
    >>= :: Maybe a -> (a -> Mayb b) -> Maybe b
 
//3. Maybe Monad 实现
instance Monad Maybe where
    return x = Just x
    Nothing >>= func = Nothing
    Just x >>= func = func x
 
//4. Haskell 运行效果
    //4.1 定义一个函数
    half x = if even x
        then Just (x `div` 2)
        else Nothing
     
    //4.2 运行效果
    *Main> Just 20 >>= half 
    Just 10
    *Main> Just 10 >>= half 
    Just 5
    *Main> Just 5 >>= half 
    Nothing
 
    //4.3 链式操作
    *Main> Just 20 >>= half >>= half >>= half
    Nothing

三者之间的联系

Applicative是增强型的Functor。
Monad是增强型的Applicative。

再总结一下

一个Functor 就是实现了Functor typeclass 的数据类型。
一个Applicative 就是现实了 Applicative typeclass 的数据类型。
一个Monad 就是实现了Monad typeclass的数据类型。

ReactiveCocoa

开头讲了ReactiveCocoa是基于函数式响应式的编程思想。基于Monad的思想。我们具体来看一下。
我们知道,RAC的核心概念是Signal而Signal继承自 RACStream,看一下RACStream源码:

@interface RACStream : NSObject
+ (instancetype)empty;
 
 
// 主要看这里
+ (instancetype)return:(id)value;
- (instancetype)bind:(RACStreamBindBlock (^)(void))block;
 
 
- (instancetype)concat:(RACStream *)stream;
 - (instancetype)zipWith:(RACStream *)stream;
@end

我们可以看到 RACStream 中有两个方法

  • + (instancetype)return:(id)value;
  • - (instancetype)bind:(RACStreamBindBlock (^)(void))block;

return:方法的功能就是将一个值 value放入 RACStream上下文中;
bind:方法的功能则是将一个 RACStreamBindBlock类型的 block应用到一个在 RACStream上下文中的值(receiver),并返回另一个在 RACStream
上下文中的值。
,RACStreamBindBlock类型的 block就是一个接收一个普通值 value但是返回一个在 RACStream上下文中的值的“函数”;

RACStream与Monad 比较一下

// 1. Monad 定义
class Applicative m => Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b
 
//2. 假设m 是RACStream 类型
class Applicative RACStream => Monad RACStream where
    return :: a -> RACStream a
    (>>=) :: RACStream a -> (a -> RACStream b) -> RACStream b

其中:
return :: a -> RACStream a就对应 + (instancetype)return:(id)value;
(>>=) :: RACStream a -> (a -> RACStream b) -> RACStream b
则对应 - (instancetype)bind:(RACStreamBindBlock (^)(void))block;

是不是一样一样的。

参考资料:

http://blog.leichunfeng.com/blog/2015/11/08/functor-applicative-and-monad/
《Haskell趣学指南》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容