#尾递归 在计算函数 f(e1, ..., en)时,由于Scala采用class-by-value的模式,所以首先会计算函数参数e1, ..., en,将参数的计算结果v1, ..., vn带回到函数 f()中。 对比以下两个函数 gcd()计算两个整数的最大公约数,和 factorial()斐波那契函数。
def gcd(a: Int, b: Int): Int =
if (b == 0) a else gcd(b, a % b)
def factorial(n: Int): Int =
if(n == 0) 1 else n * factorial(n - 1)
对 gcd(14, 21)函数按照call-by-value的方式计算:
gcd(14, 21)
→ if (21 == 0) 14 else gcd (21, 14 % 21)
→ if (false) 14 else gcd (21, 14 % 21)
→ gcd(21, 14 % 21)
→ gcd(21, 14)
→ if (14 == 0) 21 else gcd (14, 21 % 14)
→ gcd (14, 7)
→ gcd (7, 0)
→ if (0 == 0) 7 else gcd (0, 7 % 0)
→ 7
计算factorial函数:
factorial(4)
→ if (4 == 0) 1 else 4 * factorial(4 - 1)
→ 4 * factorial(3)
→ 4 * (3 * factorial(2) )
→ 4 * (3 * (2 * factorial(1)) )
→ 4 * (3 * (2 * ( 1 * factorial(0))) )
→ 4 * (3 * (2 * (1 * 1) ) )
→ 120
在计算 gcd()函数的时候,每次递归都是调用函数的最后一部分,并且不包含前一次递归的内容。而 factorial()在每次递归时,都会包含前一次递归中的信息。前一种递归方式被称为“尾递归”(tail recursion),函数在每一次调用过程中都只调用了自己最后一步的操作,这是函数就可以复用其栈。通常来说,如果一个函数的最后一步操作是调用一个函数(这个函数可以是其本身),那么一个栈帧(stack frame)就实现所有的调用,这就叫做尾调用。
现在,我们将factorial()函数改写为tail-recursion的模式
def factorial(n: Int): Int = {
def loop(acc: Int, n: Int): Int = {
if (n == 0) acc
else loop( acc * n, n - 1 )
}
loop(1, n)
}
#高阶函数
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数 在数学中高阶函数也叫做算子(运算符)或者泛函,常见的例子就是微积分中的导数。在无类型lambda演算,所有函数都是高阶的;在有类型lambda演算(大多数函数式编程语言都从中演化而来)中,高阶函数一般是那些函数型别包含多于一个箭头的函数。在函数式编程中,返回另一个函数的高阶函数被称为Curry化的函数。
##函数作为参数
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b ) 0
else f(a) + sum(f, a + 1 , b)
def sumInts(a: Int, b: Int): Int = sum(id, a, b)
def sumCubes(a: Int, b: Int): Int = sum(cube, a, b)
def id(x: Int): Int = x
def cube(x: Int): Int = x * x * x
函数 sum()就是一个高阶函数,它接受一个函数作为参数,并且要求这个函数要接受一个整数作为参数,最后返回一个整数。 “A => B” 声明了一个函数的类型,这个函数接受一个类型A的参数,并且返回一个类型B的值。
##匿名函数 为了简洁Scala的语法,Scala提供了匿名函数,我们将上面例子的 sumInts()和 sumCubes()函数改用匿名函数的方法表示:
def sumInts(a: Int, b: Int): Int = sum(x => x, a, b)
def sumCubes(a: Int, b: Int): Int = sum(x => x * x * x, a, b)
##柯里化(Currying)
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 Currying的动机: 由于数学中的一些分析技巧只能应用于接受一个参数的函数,而大部分函数都接受不止一个值作为参数,Currying使得这些分析技巧能够被应用到所有能Currying的函数上。
而在计算机科学上Currying提供了一个在简单理论模型中研究多参函数的方法(例如只接受一个参数的λ演算模型); Currying使函数表达更加简洁。
好,我们现在再次改写 sum()函数,使其柯里化。
def sum(f: Int => Int): (a: Int, b: Int) => Int = {
def sumF(a: Int, b: Int): Int =
if (a > b) 0
else f(a) + sumF(a + 1, b)
sumF
}
这次我们在 sum()函数中定义了一个 sumF()函数,并将其作为 sum()函数的返回值,同时我们也声明了 sum()的返回类型为 (a: Int, b: Int) => Int,接受两个整型作为参数并返回一个整型的函数。 接下来我们再根据新的 sum()函数,修改 sumInts()和 sumCubes()
def sumInts = sum(x => x)
sumInts(5, 6) // 调用方式不发生改变
def sumCubes = sum(x => x * x * x)
sumCubes (5, 6) // 调用方式不发生改变
现在新的 sumInts()和 sumCubes()是不是要比以前简洁多了呢?其实我们还可以直接跳过 sumInts(), sumCubes()这些中间函数,利用我们最开始定义的 id(), cube()来实现函数的调用。
sum(cube)(1, 10) == (sum(cube))(1, 10)
由于定义一个返回函数的函数在函数式编程中太有用了,Scala专门提供了语法糖支持,利用语法糖可以对 sum()进行如下改写:
def sum(f: Int => Int)(a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(a + 1, b)
我们现在Currying推广到拥有n个参数的情况:
def f(args_1)...(args_n) = E
// 当 n > 1 时
→ def f(args_1)...(args_n-1) = { def g(args_n) = E; g }
// 匿名化函数 g()
→ def f(args_1)...(args_n-1) = { args_n => E }
// 在对args_n-1重复上述步骤
→ def f(args_1)...(args_n-2) = { args_n-1 => (args_n => E) }
.
.
.
→ def f = (args_1 => (args_2 => ...(args_n => E)))