Functional Programming with RXCollections

###Higher-Order Functions
函数式编程概念的关键是高阶函数。根据WIKI的定义,高阶函数要满足下面两个条件:

  • 函数的输入参数有一个或多个是函数。
  • 返回值是一个函数。

在Objective-C中,常以块(Block)作为函数。

在Apple的基础库Foundation中,我们可以很容易地找到一些高阶函数。例如一列包含数字的数组:

NSArray *array = @[@(), @(2), @(3)];

我们可能需要枚举数组,对每个元素做一些操作。“好”,你说,“我来写个循环吧。”
别这样写,兄弟。有个数组的高阶函数我们可以用。代码如下:

for (NSNumber *number in array){
    NSLog(@"%@", number);
}    

与应用高阶函数的如下代码相同:

[array enumerateObjectsUsingBlock^(NSNumber *number, NSUInteger idx, BOOL *stop){
    NSLog(@"%@", number);
}]

“但是为什么?”,你会问我,“这不是有更多的代码了么?”是的,的确如此,但这只是通往函数式第一步。正如上一章所述,我们抽象了怎样去完成任务,而把注意力集中了任务本身上。这将会有回报,相信我。

在实际中,高阶函数是对我们做事情的抽象,不幸的是,这需要在基础类库中进行扩展。为了更进一步,让我们转向开源社区。

Installing RXCollections

RXCollections,是我朋友Rob Rix写的一个Objective-C高阶函数库。
首先,我们需要一个Xcode 工程。创建一个Playground项目名称的工程,选择Single View Application作为项目模板。大多数的代码,将写在应用程序代理(app delegate)中。在这本书中,将用”FRP”做为类名的前缀。下一步,需要安装RXCollections库。我们将应用CocoaPods来安装,因为这是一种非常容易的安装方式。确保你的机器上安装了CocoaPods,请在命令行输入以下命令确保已经安装。

sudo gem install cocoapods

当提示的时候输入你的密码,一旦CocoaPods已经安装了,用cd命令导航到你最新创建的Xcode工程目录,运行如下命令:

pod init

这个命令将会产生一个空的Podfile文件在目录下。

#Uncomment this line to define a global platform for your project
#platform:ios,"6.0"

target "Playground" do

end

target "PlaygroundTests" do 

end

打开你最喜欢的文本编辑器,无疑是VIM, 反注释掉#platform:ios,"6.0",并且增加一行pod RXCollections, 1.0,如下:

platform:ios,"6.0"

target "Playground" do

pod 'RXCollections','1.0'

end

target "PlaygroundTests" do

end

很好,返回命令行,执行如下命令:

pod install

这将会安装RXCollections,并会创建一个新的Xcode工作空间文件。关闭Xcode工程并打开Xcode工作空间。找到并打开应用程序代理文件(application delegate.m),在文件的头部引用RXColletion:

#import <RXCollections/RXCollection.h>

在application:didFinishLauchingWithOptions:方法中,创建我们刚才讲的数组:

NSArray *array = @[@(1), @(2), @(3)];

现在,我想我们已经准备好了。

###Map(映射)
我们要讲的第一个高阶函数是’Map’。从抽象意义上讲,map是把一个链表转换另一个相同长度的链表,对于原链表中的每个值都映射到一个新的值在新的链表中。对一个链表进行平方映射如下:

map(1, 2, 3) => (1, 4, 9)

当然,这只是伪代码,并且一个高阶函数会返回另一个函数,而不是一个链表。所以,map在RXCollection中怎么操作呢?
我们将通过rx_mapWithBlock:方法来完成,如下。

NSArray *mappedArray = [array rx_mapWithBlock:^id(id each){
    return @(pow([each integerValue], 2));
}];

这完成了同样的映射,如果我们有打印数组,将会看到如下内容:

{
    1,
    4,
    9
}

完美,注意到没,rx_mapWithBlock:方法不是一个真正的map,因为它不是一个高阶函数(没有返回一个函数)。在下一章节我们将讨论ReactiveCocoa库中的map。
注意到rx_mapWithBlock:方法并不是改变没来数组中的值,来返回数组。从这个意义上来讲,基础类库对函数范式是非常好的,因为基础类库中NSArray默认是不可变的。
假如通过命令式来完成,其代码如下:

NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:array.count];
for(NSNumber *number in array){
    [mutableArray addObject:@(pow([number integerValue], 2))];
}
NSArray *mappedArray=[NSArrayarrayWithArray:mutableArray];

这有很多代码,并且mutableArray局部变量污染了词法作用域。
正如你看到的,map是一个非常有用的函数,当你想把一个链表转换成另一个链表时。

Filter(过滤)

在ReactiveCocoa中,将使用到的另一个高阶函数方法是filter方法。过滤一个链表将会返回一个新的链表,新链表中只包含原链表中的元素,并经过滤条件踢出掉不符合条件的元素。我们来看一个实例。

NSArray *filterArray = [array rx_filterWithBlock:^BOOL(id each){
    return ([each integerValue] % 2 == 0);
}]

filterArray现在只是@[@(2)]。作为对比,不通过抽象,我们来做这项工作:

NSMutableArray *mutableArray=[NSMutableArray arrayWithCapacity:array.count];
for(NSNumber *number in array){
    if ([number integerValue] % 2 == 0) {
        [mutableArray addObject:number];
    }
}

NSArray *filteredArray=[NSArray arrayWithArray:mutableArray];

看到没,你可能也写了如上的代码,日常工作中,经常的需要写map和filter,应用高阶函数,能大大的提高效率。

Fold(折叠)

折叠是一个非常有趣的高阶函数,它组合链表中的每个元素成一个单独的值,基于这个原因,它也常被称为组合。
一个简单的折叠能应用到对一个整形数组的各个元素计算求和上。

NSNumber *sum=[array rx_foldWithBlock:^id(id memo,id each){ 
    return @([memo integerValue] + [each integerValue]);
}];

这会产生值@(6),数组的每个值将会按顺序调用,memo作为前一个块的返回参数(其初始值为nil)。
这不是很有趣,如果我们能够给memo属性提供初始值,将会更有趣。幸运的是,存在这样一个方法:

[[array rx_mapWithBlock:^id(id each){ 
    return [each stringValue];
}] rx_foldInitialValue:@""block:^id(id memo,id each){
    return [memo stringByAppendingString:each];
}];

这段代码的结果是@”123”, 让我们仔细看下,这是怎么做到的。开始我们把NSNumber实例映射到NSString表达,然后我们做了一个折叠,memo为空字符串初始值传入。

没有RXCollections,能做到这些么?当然,但是显然,当写的时候,“什么,而不是怎么”的方法避免去思考怎样的问题,更重要的是,当读的时候。

###Performace(性能)
前面的章节,特别是代码,你想知道它的性能怎么样。例如,如果一个很长的数组,那么,当向前一个结果每添加一个字符的时候,将会创建一个即时的字符串,这会花费更长的时间相比于通过命令式的方式编程。

的确如此,但幸运的是,在大多数情况下,现在的计算机(包括iPhones)足够强大,这些计算花费可以忽略。CPU时间是廉价的,而你的时间是宝贵的。牺牲一个换取另一个是比较好的方式,当有性能瓶颈时,你再核查代码提高性能,确定取舍。

###Conclusion
通过这章,你看到怎么不依靠可变变量来操作链表。我们不需要担心可变变量,因为RXColleciton为我们抽象了,在其内部使用可变变量。
(这并不是说你不需要去熟悉RXCollections的源代码,不需要去完成你的任务。)
在最后的一个例子中,我们应该发现,为了得到一个更复杂的结果怎样的应用链式操作。在下一章,我们将深入链式操作,事实上,这是ReactiveCocoa的一个主要原则。

在下一章,我们将更多的讨论map, filter, fold。我们不但会在链表上应用这些高阶函数,还会在流上应用,并且会介绍另外一些高阶函数。

翻译自:Functional Reactive Programming on iOS – Ash Furrow. 转载请著名出处,保留链接。