Scala By Example: 一等公民

来源:互联网 发布:淘宝星级怎么看 编辑:程序博客网 时间:2024/05/01 12:06

匿名函数

   1: def sum(f: Int => Int, a: Int, b: Int): Int =
   2:   if(a > b) 0 else f(a) + sum(f, a + 1, b)
   3:  
   4: def sumSquares(a: Int, b: Int): Int =
   5:   sum((x: Int) => x * x, a, b)

在这个最基本的示例中,sum 接受了一个函数 f 作为参数。这里,sum 就是一个 higher-order 函数。而在 sumSquares 中,我们传入了另外一个匿名函数 (x: Int) => x * x 作为 sum 的参数。注意,由于在 sumSquares 中 a “必定”是 Int 类型,因此编译器将可以推断出传入 sum 的函数的参数类型,所以传入的函数可以略去类别声明。同时,当传入的函数只有一个参数且类型被省略的时候,括号也是可选的。也就是说,上例可以简化为:

   1: def sumSquares(a: Int, b: Int): Int = sum(x => x * x, a, b)

事实上,匿名函数并不是 Scala 的核心语法,它总是会被扩展成一个具名的函数,所以,可以把匿名函数视作一个语法糖。

Currying

虽然通过传递函数作为参数能够极大的简化代码,但是在上例中,两个函数都包含了同样的 a 和 b 两个参数,通过 Currying 方法还可以进一步简化。假设函数 f 定义为:f(args1)...(argsn) = E 其中 args1 到 n 为一连串的参数列表,每次展开都会返回一个函数,则该函数可以展开为:f(args1)...(argsn-1) = (argsn) => E。不断重复该过程,则可以得到:f = (args1) =>...=>(argsn) => E。这种风格也就是所谓的 Currying (提出者 Haskell B. Curry)。通过 Curry 化,代码重写如下:

   1: def sum(f: Int => Int)(a: Int, b: Int): Int =
   2:     if(a > b) 0 else f(a) + sum(f)(a + 1, b)
   3:  
   4: def sumSquares = sum(x => x * x) _

原书的代码是从另外一个不同结构的代码演进过来的,因此并没有提到如何调用最终版本的 sum 函数。注意第四行结尾的下划线,它标记出了一个部分实现函数。

注:铅笔书上6.4节介绍了 Currying 的语法,但没有说原理,Example 则讲了原理,语法被一笔带过了。

练习1:将 sum 改写成尾递归

练习2:把 sum 的逻辑从加法改成乘法,写成新的函数 product

练习3:通过 product 来实现阶乘

练习4:把 sum 和 product 写成一个函数

范例:定点法函数 fixed-point finding function

Fixed-Point 定义 (我觉得铅笔书更注重语法,而Example有点像初级算法书,更注重表达函数式编程的"风味",所以……学点数学吧)

   1: val tolerance = 0.0001
   2: def isCloseEnough(x: Double, y: Double) = scala.math.abs((x - y) / x) < tolerance
   3: def fixedPoint(f: Double => Double)(firstGuess: Double) = {
   4:   def iterate(guess: Double): Double = {
   5:     val next = f(guess)
   6:     if(isCloseEnough(guess, next)) next
   7:     else iterate(next)
   8:   }
   9:   iterate(firstGuess)
  10: }
  11:  
  12: // made the iteration converge by averaging successive values, this technique
  13: // of average damping is so general that it can be wrapped in another function
  14: def averageDamp(f: Double => Double)(x: Double) = (x + f(x)) / 2
  15:  
  16: def sqrt(x: Double) = fixedPoint(averageDamp(y => x / y))(1.0)

以上就是一个定点法函数及以此为基础的开方函数,注意第二行的 scala.math.abs,在“很久以前”Scala 有个 Math 对象来处理数学运算,但既然可以把 value 放在 package 下,那么为什么不用更具备 Scala 风格的方式呢?还有,记得在第四章曾经有个习题是关于开方函数的精度问题的,当时我的答案是 x - y < x * tolerance 这样的逻辑,虽然说在实际应用中,你不会遇到 x 为 0 的问题,但是即使只是为了保持警觉,采用我的逻辑也是值得的。另外值得注意的就是 average damping 方法,使用平均值衰减的方法可以保证你的函数逐步收敛到一个定点数,而不是因为改变太大而发散导致死循环。

练习5:写一个开立方根的函数

Technorati 标签: Scala
原创粉丝点击