原文出处:http://ios.jobbole.com/86815/、
一函数响应式编程
说到函数响应式编程,就不得不提到函数式编程,他们俩有什么关系呢?今天我们就详细的解析一下他们的关系。
现在下面有4个概念,需要我们理清一下他们之间的关系:
面向对象编程Object Oriented Programming
响应式编程Reactive Programming
函数式编程Functional Programming
函数响应式编程Functional Reactive Programming
我们先来说一说什么是函数式编程Functional Programming,我们先来看看wikipedia上的相关定义:
Functional Programming is a programming paradigm
- treats computation as the evaluation of mathematical functions.
- avoids changing-state and mutable data
总结一下Functional Pragramming具有以下几个特点:
1.函数是“第一等公民”
2.闭包和高阶函数
3.不改变状态(由此延伸出“引用透明”的概念)
4.递归
5.只用”表达式“,不用”语句“,没有副作用
接下来依次说一下这些特点:
一.函数是”第一等公民“
所谓“第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数或者作为别的函数的返回值。
Haskell,OCaml,Standard ML,Scala 和 F# 在内的大量 (函数式) 编程语言都不同程度地借鉴了frist class 的理念。
PS:世界上最纯粹的函数式编程语言非Haskell莫属。
二.闭包和高阶函数
闭包是起函数作用并且可以向对象一样操作的对象。与此类似,函数式编程语言支持高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式)作为其输入参数,在大多数情况下,他甚至可以返回一个函数作为其输出参数。这两种结构结合在一起使用可以用优雅的方式进行模块化编程,这是使用函数式编程的最大好处。
三.不改变状态
不改变状态:
函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是他的一个重要特点。在其他类型的语言中,变量往往用来保存状态state,不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子是递归。
避免使用程序状态和可变对象,是降低程序复杂度的有效方法之一,这也是函数式编程的精髓。函数式编程强调执行的结果,而非执行的过程。我们先构建一系列简单却具有一定功能的小函数,然后将这些函数进行组装以实现完整的逻辑和复杂的运算,这是函数式编程的基本思想。
引用透明:
如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。这使您可以从形式上推断程序的行为,因为表达式的意义值取决于其子表达式而不是计算顺序或者其他表达式的副作用。
面试题: 纯函数式的闭包是否满足函数式编程里面不改变函数状态的特性?
面试题:如何理解引用透明?
四.递归
函数式编程是用递归作为控制流程的机制。
五.只用“表达式”,不用“语句”,没有副作用
表达式expression是一个单纯的运算过程,总是有返回值;语句statement是执行某种操作,没有返回值。函数式编程要求,只是用表达式,不适用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
原因是函数式编程的开发动机,一开始就是为了处理运算somputation,不考虑系统的读写(I/O)。语句属于读写操作,所以就被排斥在外。
函数式编程强调没有”副作用“,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得改变外部变量的值。
最后再来说说函数响应式编程。
首先函数响应式编程肯定是满足函数式编程的上述特性的。函数响应式编程是面向离散事件流的,在一个时间轴上会产生一些离散事件,这些事件会依次向下传递。
RAC就是Cocoa框架下的函数响应式编程的实现。它提供了基于时间变化的数据流的组合和变化。
接着再来说说之前说的4种编程范式,总结出来,如果按照类似继承图谱来看的话,应该如下图:
首先在声明式编程里面有2大家族,那就是函数式编程和数据流编程,数据流编程下面就是响应式编程,而函数响应式编程是”继承”于函数式编程和响应式编程的。
面向对象编程就属于指令式编程的范畴。从上面2张图来看,我们可以很明显看出这4者是什么关系了。
二.链式调用
定义:f(x),表示的是一种态射,从x的定义域到f(x)值域的态射。如果定义域和值域是完全相同的话,这种映射也成为单元态射。那么满足单元态射的函数,就可以进行链式调用。
以RAC为例,把RACSignal链式传递下去,subscribeNext就会返回一个RACSignal,定义域和值域都是RACSignal,那么就满足了单元态射的要求,就可以链式调用下去。
面试题:组成链式调用的必要条件就是在方法里面返回对象自己
这个说法是错误,举个例子:RAC每次做信号变换的时候,都产生了一个新的信号,所以返回自己就并不是必要条件。其实如果返回自己的同类或者和自己类似的类型,里面也包含可以继续链式调用的方法,也是可以组成链式调用的。
三.关于RAC的其他一些概念
面试题:ReactiveCocoa是Facebook出的一个FRP开源库
错误,是写Github客户端时候的附属品,附带开发出的一个开源框架。
面试题:ReactiveCocoa是基于KVO的一个开源库
错误。KVO是RAC非常次要的部分,甚至可以说没有KVO,RAC依旧可以存在。
面试题:ReactiveCocoa是一个纯函数式编程的库
错误,由于Cocoa框架并不是函数式,RAC又是在Cocoa框架下,所以就不是纯函数式。在命令式编程的语言范畴里面实现纯函数编程,需要折中的方法,我们可以封装命令式编程,使其向上层可以形成纯函数式的,但是下层肯定就是命令式编程实现的。
最后我们再来区分一个概念:
面试题:RAC中Pull-driver和Push-driver的区别?
Pull-driver是指的是任何时刻,我们如果需要数据了,都可以从pull-driver里面拿走数据,因为数据先存储了。整个取数据的时间控制在调用者手上。典型的例子就是for-in循环,这就是一个pull-driver的操作。不管你循环几次,每次循环如何操作,数组或者字典里面的数据都一直存在在那里,“躺”在那里。
Push-driver是相反的,在任何时刻,当有数据或者事件产生,都会push给你,如果你此时没有处理,该事件或者数据就丢失了。整个取数据的时间并不控制在调用者的手里。
Pull-driver可以类比看书,知识和文字不管你看不看,一直都在书里。
Push-driver可以类比看电视,节目不管你看不看,都一直播放,你错过了就是错过了。
在RAC里面,Sequence就是一个pull-driver,Signal就是一个push-driver。