Programming in Scala [5]

读书笔记(6)

Chapter 7 Built-in Control Structures

Scala的if, for, try, match都有返回值。

Scala的if

val filename =
  if (!args.isEmpty)
    args(0)
  else
    "default.txt"

这种写法有两个优点:

  1. 更短
  2. 使用val而不是var

不带elseif也有返回值,会返回Unit类型的()。当一个值的类型转换成Unit,关于值的所有消息都会丢失。所以()不表示任何值。

Scala的while

scala var line = "" do { line = readline println("read: " + line) } while (!line.isEmpty)

Scala的whiledo-while跟Java里的差不多,然而whiledo都返回不含任何值的()。这虽然有悖纯函数语言的哲学,但是方便了用户用命令式语言风格写出更具可读性的程序,比如一些算法。而有些算法虽然用while也能写,但是用递归等函数语言风格的话可能会更清晰,比如算最大公约数的算法:

def gcd(x: Long, y: Long): Long =
  if (b == 0) a else gcd(b, a % b)

总的来说,更推荐用函数风格来写程序,因为while不返回值,需要引入var

Scala的for

最简单的用法就是循环一整个collection

val fileHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
  println(file)

这种写法能循环所有collection,不只数组(必须实现了scala.Iterable这个trait的<-方法)。比如说循环一个Range类型。

for (i <- 1 to 5)
  println("Iteration " + i)

过滤

for (file <- filesHere; if file.getName.endsWith(".scala"))
  println(file)

for (file <- filesHere)
  if (file.getName.endsWith(".scala"))
    println(file)

因为for有返回值,所以for是一种expression。

可以加入更多的判断条件。

for (
  file <- filesHere;
  if file.isFile;
  if file.getName.endsWith(".scala")
) println(file)

为了更可读,可以把()换成{}。这样就可以省略掉分号;

for {
  file <- filesHere
  if file.isFile
  if file.getName.endsWith(".scala")
} println(file)

嵌套循环

def fileLines(file: java.io.File) =
  scala.io.Source.fromFile(file).getLines
def grep(pattern: String) =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
    line <- fileLines(file)
    if line.trim.matches(pattern)
  } println(file + ": " + line.trim)
grep(".*gcd.*")

中途赋值(Mid-stream assignment)

def grep(pattern: String) =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
    line <- fileLines(file)
    trimmed = line.trim
    if trimmed.matches(pattern)
  } println(file + ": " + trimmed)
grep(".*gcd.*")

例子中的trimmed = line.trim的效果类似val,只是不用标明val。减少了一次算trim的重复开销。

生成新collection

def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
  } yield file

这里在循环体之前用了yield,生成了新的collection。

当使用yield的时候,实际上是如下的结构:

for 循环判断语句 yield 循环体

yield必须放在循环体之前。(跟ruby和C#等放置的位置不同)

Scala的try

Scala的try跟其他语言的的异常处理差不多。

抛出异常

throw new NullPointerException
val half =
  if (n % 2 == 0)
    n/2
  else
    throw new Exception("n must be even")
  • 用法跟Java差不多。
  • 技术上来说,throw返回了类型Nothing。这使得上面例子的写法可以成立。

捕获异常

try {
  doSomething()
}
catch {
  case ex: IOException => println("Oops!")
  case ex: NullPointerException => println("Oops!!")
}
  • 写法跟其他语言类似。
  • 这里用了一种叫模式匹配(pattern matching)的方法,后面会提到。
  • 上面的异常处理会按顺序从上往下检查。如果无法捕获,再往外层抛出。
  • 不像Java,你无需处理已检查的异常。

finally语句

跟Java几乎一样。

生成值

try-catch-finally结构是有返回值的。比如:

val url = try {
  new URL(path)
}
catch {
  case e: MalformedURLException =>
    new URL("http://www.scala-lang.org")
}

这里有个要注意的地方,如果在Scala的finally的结构体内中途显式return返回值,这个值会覆盖之前的返回值,比如:

def f(): Int = try { return 1 } finally { return 2 }
def g(): Int = try { 1 } finally { 2 }

这里f()返回2,而g()返回1。

Scala里的match

Scala里的match,相当于其他语言的switch。不过Scala的match能让你匹配任何模式(详见Chapter 12)。

val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
  case "salt" => println("pepper")
  case "chips" => println("salsa")
  case "eggs" => println("bacon")
  case _ => println("huh?")
  • 这个例子依次把firstArg匹配"salt","chips","eggs",而通配符_则匹配默认值。
  • 不像Java里的switch,只能匹配整数类型和枚举常量,Scala的match能匹配所有常量。(虽然书里没提,实际上Java7的switch也能匹配字符串,是通过语法糖实现的,方法是先计算变量的.hashCode(),再比较常量的哈希值,最后用equals按值比较1。枚举类型本来就是语法糖,实现方法应该也类似。)
  • break不可用。(这东西本来就是语法盐。近代的语言基本都不保留了。)

没有breakcontinue也能活

有点意外的是,Scala保留了while,却非常前卫地完全去掉了breakcontinue

书里提的例子就是用递归或者用其他函数式的方法去避免用breakcontinue,介绍得不怎么详细。有空做一下深入调查再写。

Written on March 7, 2016