走过红尘的纷扰,弹落灵魂沾染的尘埃,携一抹淡淡的情怀,迎着清馨的微风,坐在岁月的源头,看时光婆娑的舞步,让自己安静在时间的沙漏里,感受淡如清风,静若兰的唯美。
本章讲述Scala
基本的控制结构,包括条件分支,循环;最后讲述自定义控制结构的实现技术,包括条件分支,循环,中止。
- 内置控制结构
- 条件分支
- 循环结构
- 自定义控制结构
- 条件分支
- 循环结构
- 异常终止
内置控制结构
在scala
中,一切皆为表达式。包括内置的控制结构,包括条件分支,循环结构,for
推导式,模式匹配,及其函数调用等。
条件分支
if-else
并非语句,而是一个表达式。它拥有一个值,要么来自if
分支,要么来自else
分支。
def abs(x: Int): Int = if (x > 0) x else -x
根据if (x > 0) x else -x
表达式的类型,可以自动推演出abs
返回值类型为Int
。
最近公共父类
两个分支的类型可以不同,将自动推演为两者之间「最近的公共父类」。例如,abs
的返回值类型被推演为None
与Some
的最近公共父类Option[Int]
。
def abs(n: Int) = if (n > 0) Some(n) else None
单分支的值
对于单分支if
,编译器默认地引入else ()
分支。
def abs(n: Int) = if (n > 0) n
等价于
def abs(n: Int) = if (n > 0) n else ()
其中,()
的类型为Unit
类型,而Unit
和Int
最近公共类型为AnyVal
,所以此时abs
的返回值类型推演为AnyVal
。
else抛出异常
当else
分支抛出异常时,函数返回值类型推演为if
分支的值类型。这是因为throw
表达式的值类型为Nothing
,它是所有类的子类。例如,abs
的返回值类型推演为Int
。
def abs(n: Int) =
if (n > 0) n else throw new IllegalArgumentException("negative")
函数式风格
一般地,要从某一个容器中获取值,必须先进行前置的状态查询。本质上,这是一种契约。但遗憾的是,这个契约并没有对用户行为形成强力的约束力,导致既有的约定常常被打破。
这是一种常见的实现模式,例如调用Option
在调用get
之前,先调用isEmpty
查询是否为空。可以通过封装改善设计,Option
的getOrElse
替代原来的if-else
实现,不仅改善了表达力,而且消除了重复代码。
其中,Options
的getOrElse
方法类似于如下实现。
sealed trait Option[+A] {
def isEmpty: Boolean
def get: A
def getOrElse[B >: A](default: => B): B =
if (isEmpty) default else get
}
循环结构
在Scala
中,while/do-while
不是语句,而是表达式,只不过它们返回的类型为Unit
。例如,factorial
用于求解n(n <= 16)
的阶乘,使用while
迭代的实现方式如下。
def factorial(n: Int): Long = {
var r = 1; var m = n
while (m > 0) {
r = r * m
m -= 1
}
r
}
for推导式
可以使用for
推导式改善程序的表达力。
def factorial(n: Int): Long = {
var r = 1
for(i < 1 to n) r = r * i
r
}
i < 1 to n
称为for
推导式的「生成器」。for
推导式是Scala
的重要特性之一,将在后续章节重点介绍。
线性递归
也可以使用「模式匹配」实现factorial
的递归算法。
def factorial(n: Int): Long = n match {
case 0 => 1
case _ => n * factorial(n - 1)
}
但是,该实现不具有尾递归的优化效果。其中,模式匹配是Scala
的重要特性之一,将在后续章节重点介绍。
尾递归
为了得到优化的尾递归实现,可以使用「本地方法」设计迭代结构来实现,并显式地加上@tailrec
的注解,保证编译时尾递归的优化能力。
def factorial(n: Int): Long = {
@annotation.tailrec
def loop(r: Int, n: Int): Long = n match {
case 0 => r
case _ => loop(r * n, n - 1)
}
loop(1, n)
}
函数式风格
可以使用foldLeft
实现factorial
,这种函数式的风格简单优雅。
def factorial(n: Int): Long =
(1 to n).foldLeft(1) { _ * _ }
自定义控制结构
Scala
是一门具有高度可扩展性的程序设计语言,它可以设计出自定义的控制结构,以便提升代码复用价值,并改善代码的表达力。
条件分支
可以使用Scala
设计「修饰的条件分支」,它将判断条件置后,而突出执行的意图。首先定义一个Conditional
的隐式类,它支持iff/unless
两种类型的后缀修饰符,它们两者刚好形成相反的语义。
implicit class Conditional[+T](left: => T) {
def iff(right: => Boolean): Option[T] = if (right) Some(left) else None
def unless(right: => Boolean): Option[T] = if (!right) Some(left) else None
}
假设,在当前作用域内,隐式类Conditional
可见,则可以按照如下方式使用iff/unless
。
n iff n % 2 == 0 // None
{ println(n) } unless n % 2 == 0
循环结构
可以设计一个指令式的until
函数,它将循环调用block
,直至条件cond
为真为止(与while
相反)。
@annotation.tailrec
def until(cond: => Boolean)(block: => Unit) {
if (!cond) {
block
until(cond)(block)
}
}
当调用until
时,最后一个柯里化参数列表的调用可以使用大括号代替小括号。例如,
var n = 10
var r = 0
until (n == 0) {
r += n
n -= 1
}
事实上,它等价于
(0 to 10).sum
前者为指令式的代码风格,后者为函数式的代码风格。
异常终止
为了倡导函数式的代码风格,Scala
语言内核拒绝了指令式的break, continue
特性。但借助于Scala
强大的可扩展能力,可以模拟实现break, continue
指令的实现。
探秘forall
Traversable
是一个可以遍历的,元素类型为A
的集合。它定义了一个foreach
的内部迭代器,其它方法的实现都是基于foreach
的抽象,使用for
推导式实现算法逻辑的。
例如,forall
用于判断集合元素是否全部满足于某个谓词,否则返回false
。算法无需遍历整个集合,实现使用了break
提前终止迭代过程。
Scala
为了提高执行效率,对外提供函数式的编程接口,内部实现常常使用指令式的代码风格。
import scala.util.control.Breaks._
trait Traversable[+A] {
def foreach[U](f: A => U): Unit
def forall(p: A => Boolean): Boolean = {
var result = true
breakable {
for (x <- this if !p(x)) {
result = false
break
}
}
result
}
}
探秘breakable/break
事实上,break
的工作机制是通过异常来实现的。breakable
定义了一个执行op: => Unit
的上下文环境,break
通过抛出异常终止当前执行的op
操作。
class Breaks {
private val breakException = new BreakControl
def break(): Nothing = throw breakException
def breakable(op: => Unit): Unit =
try {
op
} catch {
case ex: BreakControl =>
if (ex ne breakException) throw ex
}
}
模拟break/continue
break
只是终止了breakable
的上下文。如果要模拟指令式的break
,可以通过将循环放在breakable
之内实现。
breakable {
for (x <- xs) {
if (cond) break
}
}
如果要模拟指令式的continue
,可以将循环放在breakable
之外实现,break
时只是终止了当前的迭代,而不是整个迭代过程。
但很遗憾,虽然它代表了continue
的语义,但这里依旧使用了break
的命名。
for (x <- xs) {
breakable {
if (cond) break
}
}