函数式语言的特性
函数式语言当然还少不了以下特性:
- 高阶函数(Higher-order function)
- 偏应用函数(Partially Applied Functions)
- 柯里化(Currying)
- 闭包(Closure)
高阶函数就是参数为函数或返回值为函数的函数。有了高阶函数,就可以将复用的粒度降低到函数级别,相对于面向对象语言,复用的粒度更低。
举例来说,假设有如下的三个函数,
def sumInts(a: Int, b: Int): Int =
if (a > b) 0 else a + sumInts(a + 1, b)
def sumCubes(a: Int, b: Int): Int =
if (a > b) 0 else cube(a) + sumCubes(a + 1, b)
def sumFactorials(a: Int, b: Int): Int =
if (a > b) 0 else fact(a) + sumFactorials(a + 1, b)
分别是求a到b之间整数之和,求a到b之间整数的立方和,求a到b之间整数的阶乘和。
其实这三个函数都是以下公式的特殊情况
三个函数不同的只是其中的f不同,那么是否可以抽象出一个共同的模式呢?
我们可以定义一个高阶函数sum:
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b) 0
else f(a) + sum(f, a + 1, b)
其中参数f是一个函数,在函数中调用f函数进行计算,并进行求和。
然后就可以写如下的函数
def sumInts(a: Int, b: Int) = sum(id, a, b)
def sumCubs(a: Int, b: Int) = sum(cube, a, b)
def sumFactorials(a: Int, b: Int) = sum(fact, a, b)
def id(x: Int): Int = x
def cube(x: Int): Int = x * x * x
def fact(x: Int): Int = if (x == 0) 1 else fact(x - 1)
这样就可以重用sum函数来实现三个函数中的求和逻辑。
(示例来源:https://d396qusza40orc.cloudfront.net/progfun/lecture_slides/week2-2.pdf)
高阶函数提供了一种函数级别上的依赖注入(或反转控制)机制,在上面的例子里,sum函数的逻辑依赖于注入进来的函数的逻辑。很多GoF设计模式都可以用高阶函数来实现,如Visitor,Strategy,Decorator等。比如Visitor模式就可以用集合类的map()或foreach()高阶函数来替代。
函数式语言通常提供非常强大的集合类(Collection),提供很多高阶函数,因此使用非常方便。
比如说,我们想对一个列表中的每个整数乘2,在命令式编程中需要通过循环,然后对每一个元素乘2,但是在函数式编程中,我们不需要使用循环,只需要使用如下代码:
scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)
scala> numbers.map(x=>x*2)
res3: List[Int] = List(2, 4, 6, 8)
(示例来源:Programming Scala: Tackle Multi-Core Complexity on the Java Virtual Machine一书的Introduction)
其中x=>x*2是一个匿名函数,接收一个参数x,输出x*2。这里也可以看出来函数式编程关注做什么(x*2),而不关注怎么做(使用循环控制结构)。程序员完全不关心,列表中的元素是从前到后依次计算的,还是从后到前依次计算的,是顺序计算的,还是并行进行的计算,如Scala的并行集合(Parallel collection)。
使用集合类的方法,可以使对一些处理更简单,例如上面提到的求阶乘的函数,如果使用集合类,就可以写成:
def fact(n: Int): Int = (1 to n).reduceLeft((acc,k)=>acc*k)
其中(1 to n)生成一个整数序列,而reduceLeft()高阶函数通过调用匿名函数将序列化简。
那么,在大数据处理框架Spark中,一个RDD就是一个集合。以词频统计的为例代码如下:
val file = spark.textFile("hdfs://...")
val counts = file.flatMap(line => line.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
counts.saveAsTextFile("hdfs://...")
(示例来源:https://spark.apache.org/examples.html)
示例里的flatMap(),map(),和集合类中的同名方法是一致的,这里的map方法的参数也是一个匿名函数,将单词变成一个元组。写这个函数的人不用关心函数是怎么调度的,而实际上,Spark框架会在多台计算机组成的分布式集群上完成这个计算。
此外,如果对比一下Hadoop的词频统计实现:WordCount - Hadoop Wiki ,就可以看出函数式编程的一些优势。
函数式编程语言还提供惰性求值(Lazy evaluation,也称作call-by-need),是在将表达式赋值给变量(或称作绑定)时并不计算表达式的值,而在变量第一次被使用时才进行计算。这样就可以通过避免不必要的求值提升性能。在Scala里,通过lazy val来指定一个变量是惰性求值的,如下面的示例所示:
https://www.zhihu.com/question/28292740