戏说R语言系列4
上一集讲到 R语言是 S语言的一支方言。其实呢, R语言还受另外一款语言的影响,就是Scheme。
Scheme是一种函数式编程语言,是Lisp的两种主要方言之一(另一种为 Common Lisp)。不同于 Common Lisp,Scheme遵循极简主义哲学,以一个小型语言核心作为标准,加上各种强力语言工具(语法糖)来扩展语言本身。
上面这段话摘自维基百科。
廖雪峰 在他的 python教程里这样描述函数式编程。
函数式编程......其思想更接近数学计算。
在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
David Springate 在他的博客里讲到了函数式编程的特征,以及函数式编程在 R语言里的应用。
我讲几个印象比较深刻的特征。
向量运算
for循环在各种语言都是很普遍的一种语法。程序员们在写多层复杂嵌套的for循环上都有一些拿手的绝活。
但实际上,R语言是极其不鼓励写for循环的,而是鼓励使用向量计算这种较为抽象的方法。David Springate举了下面这个例子,他分别用for循环和向量计算求1~200000之间的偶数。
for循环计算 向量计算在这里,我们也计算了这两种方法的运行时间,很明显,用for循环计算并返回结果的时间(31秒多)要远远高于向量计算的时间(0.02秒)。从计算效率上讲,向量计算要优于for循环计算的,这也是为什么 R语言不鼓励使用for循环的原因。
简单一点说,向量计算在处理一个数和一组数字上是没有区别的。例如,求数字2的平方根,在R语言里执行sqrt(2)就可以了。如果求一组数字,例如 1、2、3、4、5,每个数字的平方根,并不需要写成 sqrt(1)、sqrt(2)、sqrt(3)、sqrt(4)、sqrt(5),只要写成 sqrt(1:5) 就可以了。也就是说,sqrt() 这个函数是向量化的,R语言里大部分的函数都是向量化的,所以,这也是R语言可以大量使用向量计算的原因。
C++本来是没有向量运算的,后来出现了Rcpp(为了提升R语言的执行效率,一种可以在R程序里嵌入C++程序,也可以在C++程序里嵌入R程序的工具),考虑到R用户使用向量计算的习惯,特意加了语法糖:可以用向量运算来编写C++程序。实际上,不同语言的相互学习、渗透已经很普遍了。
Lisp甚至没有for循环的语法。R语言虽然是强函数式编程语言,但还是保留了for循环的语法,否则学的人可能就更少了。
高阶函数
高阶函数和向量计算有点类似,如下图所示。高阶函数将输入向量(或矩阵、list、dataframe等数据结构)的每个元素按顺序代入函数中,并将求出的结果按顺序返回。
摘自David Springate的讲义廖雪峰的python讲义里也提到了python的几个高阶函数,分别是 map/reduce 、filter、sorted,我不知道这是不是python的全部高阶函数。
R语言有一个大类的高阶函数被称为 apply族群,之所以叫“族群”,是因为在最基本的apply()、lapply()、sapply()、Reduce()的基础上,发展了很多工具。例如 Hadley Wickham(前几集一直再讲他)的plyr、dplyr,除此之外,还有更多的类似的工具。作为一种函数式编程语言,只要能想象到的抽象计算,都可以转换成高阶函数。
我个人更喜欢 Hadley Wickham 的 plyr,更有魔力的一种工具。
闭包(closure)
我在javascript看到闭包的概念,R语言也有闭包的概念。
John D Cook 这句 “An object is data with functions. A closure is a function with data.” 说的挺好,对象就是带着函数的数据,闭包就是带着数据的函数。
摘自David Springate的讲义闭包就是在原有函数的基础上去生产新的函数,或者简称为函数的函数,也就是函数本身可以作为参数代入新的函数里。一些纯函数式编程语言甚至没有变量这个东西,都是函数,想想挺神奇的。
David Springate在讲义里举例说明了闭包的优势。这个案例讲的是如何设计一个 bootstrap 的抽样方案来做一个简单的回归分析。如下图所示。
源代码摘自David Springate讲义这个是用闭包来编写的,boot_lm是一个函数,iris_boot是在boot_lm基础上的一个新的函数。真正执行的是bstrap那一步。所以说,闭包这种方式的一个好处是,编程者可以在函数定义上不断优化。
同样的问题,David Springate换了一种非函数式编程方法。如下图所示。
源代码摘自David Springate讲义David Springate 这样评价这段代码 “This ugly beast is full of fors and ifs and braces and brackets and double brackets. It has a load of extra boilerplate code to define the variables and fill the matrices.” ,这个就不翻译了,看起来很愤怒。出于专业精神,我们还是要知道 shit 在哪里。
主要问题其实就是,没有把函数的定义过程和计算过程分离开来,如果执行不下去的话,很难发现到底是哪里出错了。
很多评论说,函数式编程语言是 “ free-debug”的,大概意思就是,只要函数编的好,就不会有太多问题。
网上有一些文章对面向对象编程和函数式编程进行了比较。印象里说 javascript 因为从函数式编程转到面向对象编程,才有了今天的辉煌。也有说,C语言需要一年时间完成开发某个功能,Lisp语言只需要不到三星期。
我觉着实际上没那么夸张。