Scala By Example: 表达式与函数

本文探讨了Scala中的表达式及参数处理方式,包括Call-by-value与Call-by-name的区别,并通过实例展示了条件表达式、递归函数及尾递归的应用。此外,还介绍了如何优化算法性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

表达式及参数

def x = e 这是一个非常简单的表达式,当Scala执行这句语句的时候并不会立刻去计算/获取e的值,一直到程序需要 x 的值才会进行实际的替换(以前用过惰性加载,这个得叫惰性赋值吧)。复杂的表达式可以通过逐项替换逐渐简化成简单表达式,术语为reduction。

Scala用两种方式调用函数的参数:Call-by-value, Call-by-name。默认情况下,Scala使用Call-by-value方式,可以使用 => 来指定Call-by-name方式:

   1: def loop: Int = loop //don't try this at home!
   2:  
   3: // x: Call-by-value; y: Call-by-name
   4: def constOne(x: Int, y: => Int) = x
   5:  
   6: // if loop is Call-by-name and never used, so Scala avoids the evaluation
   7: constOne(1, loop) // return 1
   8:  
   9: // if loop is Call-by-value, when Scala try to reduce the expression, the
  10: // same term reduces repeatedly to itself, hence evaluation does not terminate
  11: constOne(loop, 1) // gives an infinite loop. 

Call-by-value可以避免重复对参数项进行计算及赋值(evaluation),而Call-by-name可以避免对函数没有用到的参数项进行计算及赋值。前者至少且只进行一次evaluation,通常比较有效率,但有可能陷入死循环;后者则每次取用这个参数都进行一次evaluation。

条件表达式:if-else

在 Java 或 Groovy 中,if-else 只能作为单纯的控制结构,但在 Scala 中,if-else (我猜)会返回值。因此在 Scala 中不需要三元表达式 a : b ? c,直接使用 if-else 结构即可。

例程:牛顿法开平方

   1: #!/bin/env scala
   2: !#
   3: def abs(x: Double) = if (x >= 0) x else -x
   4: def square(x: Double) = x * x
   5:  
   6: def sqrtIter(guess: Double, x: Double): Double =
   7:     if(isGoodEnough(guess, x)) guess else sqrtIter(improve(guess, x), x)
   8:  
   9: def improve(guess: Double, x: Double) = (guess + x / guess) / 2
  10: def isGoodEnough(guess: Double, x: Double) = abs(square(guess) - x) < 0.001
  11:  
  12: def sqrt(x: Double) = sqrtIter(1.0, x)
  13:  
  14: println(sqrt(25))
  15: println(sqrt(0.000036))

在例程中可以看到,涉及递归的函数需要显式声明返回类型(因为逻辑上编译器无法在有限步数内推算出返回类型),对于普通的函数,一般编译器都能根据上下文进行类型推断,此时加不加显式声明就是风格问题了。铅笔书推荐不要加,Scala By Example则推荐加,在此不做深究,宗教信仰么……略过。

嵌套函数

之前的例程虽然实现了sqrt,但是却暴露出improve和isGoodEnough这两个不太可能被重用的方法,可以利用Scala嵌套函数的语法进行改进:

   1: def sqrt(x: Double) = {
   2:   def sqrtIter(guess: Double): Double =
   3:       if(isGoodEnough(guess)) guess else sqrtIter(improve(guess))
   4:  
   5:   def improve(guess: Double) = (guess + x / guess) / 2
   6:   def isGoodEnough(guess: Double) = abs(square(guess) - x) < 0.001
   7:   sqrtIter(1.0)
   8: }

进行嵌套后,x 在整个 block 范围内都是可见的,所以三个嵌套函数之间不需要再互相传递 x。

尾递归
   1: // Greatest common divisor
   2: def gcd(a: Int, b: Int): Int = if(b == 0) a else gcd(b, a % b)
   3:  
   4: gcd(14, 21)
   5: // -> gcd(21, 14)
   6: // -> gcd(14, 7)
   7: // -> gcd(7, 0)
   8: // -> return 7
   9:  
  10: def factorial(n: Int): Int = if(n == 0) 1 else n * factorial(n - 1)
  11:  
  12: factorial(3)
  13: // 3 * factorial(2)
  14: // 3 * (2 * factorial(1))
  15: // 3 * (2 * 1)
  16: // return 6

考察这两个递归函数,求最大公约数 gcd 的时候,每一次递归后,表达式都保持了同样的规模;而在求阶乘 factorial 的时候,每一次递归都把表达式扩展为更长的表达式。前者即所谓的尾递归(就像一连串调用首尾相连,到最后从尾巴上跳回原始调用)。由于尾递归在每次递归过程中新的表达式(理论上)都可以覆盖前一表达式的 stack 空间,因此其空间效率较高。另外,如果一个函数的最后一个调用是其它的函数,那么理论上只需要单一的stack即可,这样的调用称为“尾调用”(Tail Call)。但由于JVM缺乏堆栈级别的重用机制,所以一般 Scala 里只有函数尾调用自身的时候才能重用栈。

练习1:优化开方函数,原始的代码在计算非常小的数时精度极差,在算非常大的数时可能死循环,为什么,试修改

练习2:将阶乘写成尾递归的形式

PS: 没想到这本书居然会讲到算法和性能的问题

PS II: 范例看得多了,慢慢的感觉就来了。

PS III: 看惯了java doc,看Scala的文档格式好吃力

Technorati 标签: Scala
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值