理解函数语言
对于熟悉对象语言的程序员来说,函数语言显得有些深奥,特别是满眼看不懂的名词,什么 lambda,high order function, currying,很多不知所谓的名词构成了一道阻碍学习之路的长城。本文的目的就是介绍一下函数语言的特等,其与对象语言的主要区别在哪里,以及对函数语言普遍的误区。
误区 1: 函数语言就是操作各种函数的语言
函数语言这个名词自身就很容易引起这种偏颇的理解,很多人在初学函数语言的时候,看到示例程序里面把函数作为参数传来传去,就会以为函数语言的目的就是把函数作为参数。所有程序的目的都是为了操作数据,虽然函数语言提供了把函数作为参数的特性,但是最终的目的仍然是为了操作数据。把函数作为参数只是为了更方便得操作数据。比如 map 函数,用于把一个列表映射成另一个列表:
List(1, 2, 3) map { _ + 1 } // get List(2, 3, 4) |
虽然 map 函数的参数是一个函数,但是它的目的是为了生成数据的列表,所有的函数都是操作数据的,脱离了数据,函数也就毫无意义
误区 2: 函数语言很难理解
其实正好相反,函数语言提供了更高层的抽象,实际上比其他语言更容易理解。觉得函数语言难于理解是由于受现有语言影响太深。比如下面两段代码:
for (int i = 0; i < 10; i++) print(i); |
(0 until 10) foreach print |
如果拿给不懂任何编程的人看,他更容易理解哪个呢?对于更加复杂的代码,很多人理解的时候,是使用脑内模拟执行,跟踪调试的方法,这种理解程序的方法是最不可取的!很多关于编程的等级考试以及招聘笔试都会有理解程序的题目,回想一下你是怎么做这些题目的?是否是脑内模拟执行的呢?如果是,你已经被万恶的教育制度毒害了,请立刻忘记此方法!下面教你学会函数语言的思考方式。
函数式思考 1: 考虑整体,不要考虑个体
写一段程序,求 1 + 2 + 3 + … + 100
很多人就在想了,那不就是 1 + 2 = 3,然后再 + 3 = 6,然后再 + 4 = 10 …… 于是
int sum = 0; for (int i = 0; i <= 100; i++) sum += i |
很对,但是你的思想被局限在一个一个数相加的过程中了,你*一步一步*得把所有数加起来。现在换个思维方式,1 加到 100,无非就是把所有数都加起来么,那么我加起来就对了么:
(1 to 100) reduce { _ + _ } |
对于 reduce 函数怎么实现,大家都是高手,这里不需说明,基本所有的函数语言都有这个 reduce 的实现。这里我们把 1 到 100 这 100 个数字作为一个整体去理解,而不是分割得看成 100 个单独的数字。
函数式思考 2: 循序渐进,由简入繁
写一段程序,返回 100 内所有奇数
for (int i = 1; i <= 100; i += 2) doSth(i) |
不做评论,c/java 程序员的标准答案。现在我们重新审题,返回 100 内:
(1 to 100) |
所有奇数:
(1 to 100) filter { _ % 2 == 1 } |
这就是函数式程序员与传统程序员的区别,传统程序员:我需要 1 3 5 ….,函数式程序员:我需要 100 以内的数,我还需要一个过滤奇数的函数。
应用示例
或许所说的几点过于教条,难于理解,下面举几个简单的例子:
把一个字符序列转换成比特序列 (一字节 = 8比特)
你是不是再想:把第一字节转行成8比特,然后把第二字节转行成8比特,然后……打住!我们需要的是把毎字节转换成8比特。那么怎么把毎字节转换成8比特呢?你是不是在想:拿出第一比特,然后再拿出第二比特……再次打住!上面说了,要从整体考虑,我们要拿出所有比特:
val masks = (0 to 7).reverse map { 1 << _ } |
然后:
def charToBits(byte: Byte) = masks map { mask => (byte & mask) != 0 } |
最后,我们需要操作的是 Byte序列:
def charSeqToBits(bytes: Seq[Byte]) = bytes flatMap charToBits |
怎么样?有没有什么感悟呢?