数组取值

Scala 中可以通过 Array.apply()、Map.apply() 等方法获得这些容器中存储的值,而小括号则是 apply 方法的一个语法糖。所以 myArray(i) 等价于 myArray.apply(i)

tuple 取值

获取一个 tuple 中第 i 个元素的方法是 _i,例如 pair._1,注意这里编号是从 1 开始的,和 Haskell、ML 等语言一致。

换行符

换行符通常会被当作分号处理,所以大多数情况下行尾的分号可以省略,除了以下三种情况:

  1. 该行结尾处的符号无法使这一行合法中止,例如一个中缀运算符(加号),或者一个逗号;
  2. 下一行头部的符号无法开始一个新的语句;
  3. 该行被包含在小括号或者中括号中,因为这两个符号无法包含多个语句。

第三种情况也是 for 语句体遇到多个 filter 时要优先使用大括号的原因。

静态成员

Scala 没有静态成员这一概念,类似功能的成员被转移到 singleton objects 中。这一点倒是和 Ruby 中类对象的 eigenclass 挺像的。当一个 singleton object 的名字和某个类的名字相同时,前者被称为后者的 companion object,当然反过来,后者被称为前者的 companion class。它们可以互相访问对方的私有成员。

闭包(closure)

(x: Int) => x + more 中的 more 是一个 free variable,因为这一声明没有给它任何定义,而 x 则是 bound variable,它被赋予了函数参数的意义。

一个函数不包含 free variable 的函数被称为 closed term,这里 term 是指一段代码。而包含 free variable 的函数被称为 open term。对于 open term,程序运行时需要从环境中把对象绑到它们的 free variable 上,绑定后得到的结果就是闭包。也就是说,闭包是闭合 open term 的结果。

Scala 的闭包绑定的是变量本身,而不是它们的值。因此变量的值变化时,闭包的行为也会发生相应的变化。

括号互用

当函数只有一个参数时,传参用的小括号可以用大括号代替。这一特性配合 currying 可以比较优雅的实现一些 DSL。

Currying

Scala 的 currying 其实就是个语法糖。

def withPrintWriter(file: File)(op: PrintWriter => Unit) ...

等价于

def withPrintWriter(file: File) = (op: PrintWriter => Unit) => ...

命名空间

Scala 有两类命名空间:

  • 值(字段、方法、包和 singleton object)
  • 类型(类型和 trait)

这里字段和方法在同一个命名空间中,因此它们直接可以互相覆盖。

覆盖(Override)

Scala 要求在覆盖一个 concrete class 的成员时必须加上 override 修饰符。如果是覆盖一个 abstract class 的成员,override 修饰符是可省的。

trait

不同于 class,trait 的 super 对象是动态绑定的。

调用多个 trait 里都包含的函数时,最右边的 trait 的方法最先被调用。

模式匹配(Pattern Matching)

Scala 编译器在做模式匹配时,如果其中的变量以小写字母开头,则会被当成一个新的变量,例如

val x = 10
5 match {
    case x => println('matched')
}

这里的 case x 会匹配所有的情况,因为 x 被当成了一个新的变量,如果要使用 x 本身的值,需要把它引起来:

val x = 10
5 match {
    case `x` => println('matched')
}

Type Erasure 的限制

受限于 JVM 的 type erasure 特性,pattern match 无法检查容器存储的对象的类型:

def isIntIntMap(x: Any) = x match {
  case m: Map[Int, Int] => true
  case _ => false
}

isIntIntMap(Map("abc" -> "abc")) # true

当然,数组是例外。

(未完)