Scala笔记

该笔记主要整理自Cay S. Horstmann著,高宇翔译的《快学Scala》,偏重于记录Scala语言有特点的地方。另外,这本书写的(或许是译的?)挺搞笑的...


类型和操作符

Scala的7种数值类型(Byte、Char、Short、Int、Long、Float和Double)和Boolean类型本质是类。比如可以对数字执行方法:

1.toString() // 输出字符串"1"


Scala中操作符本质是方法,比如:

a + b 等效于 a.+(b)

事实上,Scala中方法的调用有两种方式:

1.to(10) 等效于 1 to 10

基于此,Scala的设计者没有额外提供 ++ 操作,而是采用

counter += 1counter.+=(1) 的方式提供+1操作。

一元操作符中,后置操作符的等效关系如下:

1 toString 等效于 1.toString()

四个前置操作符 +, -, !, ~ 被转换为名为 unary_ 的后置操作符,比如:

-a 等效于 a.unary_-

需要注意的是,以冒号为结尾或赋值的操作符,是右结合的,比如:

2 :: Nil 等效于 Nil.::(2)


像方法可以重载一样,Scala中操作符也可以重载


方法

在Scala中允许对函数之外的值采用类似函数调用的语法,比如:

"Hello"(4) 等效于 "Hello".apply(4)

.apply 相反,存在 .unapply 方法,在变量定义时使用,如:

val Fraction(a, b) = Fraction(3, 4) * Fraction(2, 5) 等效于 val (a, b) = Fraction.unapply(Fraction(3, 4) * Fraction(2, 5))

其中,Fraction 的伴生对象定义如下:

object Fraction {  
  def unapply(input: Fraction) =
    if (input.den == 0) None else Some((input.num, input.den))
}

而对于赋值语句,有:

f(arg1, arg2, ...) = value 等效于 f.update(arg1, arg2, ..., value)


通常来说,不带参数且不改变当前对象的Scala方法不使用圆括号,如:

"hello".distinct


控制结构

Scala中几乎所有构造出来的语法结构都有值,比如:

val s = if (x > 0) 1 else -1

在Scala中每个表达式都有一个类型。上述 if else 表达式的类型是Int,而

if (x > 0) "positive" else -1 的类型为公共超类,本例中StringInt的公共超类是Any

由于Scala中每个表达式都应该有某种值,所以当语句没有输出值时应该采用Unit类,写作()。应用到上述语句中如下:

if (x > 0) 1 else ()

在Scala中{}块表达式的值为块中最后一个表达式的值,比如:

val distance = { val dx = x - x0; val dy = y - y0; sqrt(dx * dx + dy * dy) }

需要注意的是,赋值语句的值是Unit类型。


循环 for () 中,变量 <- 表达式 组成一个生成器,若有多组生成器可采用;隔开。if 表示一个守卫

for ()中可以包含生成器、守卫和定义语句,并可以使用{}加换行的形式,比如:

for { i <- 1 to 3  
  from = 4 - i
  j <- from to 3
}

for () yield 会创建和第一个生成器同样类型的新集合,比如:

val a = Array(2, 3, 4)  
val result = for (elem <- a) yield 2 * elem  
// result为Array(4, 6, 8)

函数

对于递归函数,必须指明返回类型,比如:

def fac(n: Int): Int = if (n <= 0) 1 else n * fac(n - 1)

据说是因为解决不了面向对象语言中推断递归函数返回值类型的问题。

Scala中函数支持默认值,且使用函数时参数可指定参数名,还支持变长参数。

当函数定义时没有采用=,该函数没有返回值,称为过程,比如:

def box(s: String) {  
  var border = "-" * s.length + "--\n"
  printIn(border + "|" + s + "|\n" + border)
}

该函数只用于打印一个图像。


在Scala中可以将一系列语句归组成不带参数也没有返回值的函数(过程),比如:

def runInThread(block:() => Unit) {  
  new Thread {
    override def run() { block() }
  }.start()
}

runInThread { () =>  printIn("Hi"); Thread.sleep(1000); printIn("Bye") }  

上述例子还能通过换名调用使得参数在声明和调用时省去(),比如:

def runInThread(block: => Unit) {  
  new Thread {
    override def run() { block() }
  }.start()
}

runInThread { printIn("Hi"); Thread.sleep(1000); printIn("Bye") }  

上述方式称之为抽象控制,我们可以通过抽象控制的方式来实现类似 while 的控制语句,比如:

def until(condition: => Boolean)(block: => Unit)  {  
  if (!condition) {
    block
    until(condition)(block)
  }
}

var x = 10  
until (x == 0) {  
  x -= 1
  println(x)
}
//输出为9到0

从上述例子也能看出,与常规的换值调用不同,换名调用的参数在被调用时其参数表达式不会被求值,而是被当成无参数的函数体,整体作为参数被传递下去。


在Scala的中,函数的返回值就是函数体的值,而 return 常用在包含匿名函数的带名函数中,比如:

def indexOf(str: String, ch: Char): Int = {  
  var i = 0
  until (i == str.length) {
    if (str(i) == ch) return i
    i += 1
  } // 由于换名调用{if(str(i)==ch) return i;i+=1}实际上是匿名函数
  return -1
}

懒值

懒值可以看成是介于valdef的中间状态,比如:

val words = scala.io.Source.fromFile("/root/words.txt").mkString // 在words被定义时便读取文件

lazy val words = scala.io.Source.fromFile("/root/words.txt").mkString // 在words被首次使用时读取文件

def words = scala.io.Source.fromFile("/root/words.txt").mkString // 每次words被调用时都读取文件


对偶、映射和元组

Scala中可以采用 a -> b(a, b) 的形式来生成对偶映射是对偶的集合。

结合 forfor ((k, v) <- 映射) 处理k和v 的常用处理方式,比如:

for ((k, v) <- 映射) yield (v, k) // 交换键和值


元组是不同类型的值的聚集,比如对偶就是最简单的元组形态。比如:

val t = (1, 3.14, "Fred") // 定义一个三元组  
val second = t._2 // 获取元组中第二个组元,需要注意的是,元组中组元是从1而不是0开始的  

类、对象和特质

Scala中,同名的类和对象互称为伴生类伴生对象。通常,采用伴生对象的方式来实现类的静态方法,比如:

class Account {  
  val id = Account.newUniqueNumber()
  private var balance = 0.0
  def deposit(amount: Double) { balance += amount }
}

object Account { //伴生对象  
  private var lastNumber = 0
  private def newUniqueNumber () = { lastNumber += 1; lastNumber }
}

object可以扩展自类,比如:

abstract class UndoableAction(val description: String) {  
  def undo(): Unit
  def redo(): Unit
}

object DoNothingAction extends UndoableAction("Do nothing") {  
  override def undo() {}
  override def redo() {}
}

val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction)  

Scala中常会定义和使用对象的 apply 方法,在如下情况下 apply方法会被调用:

Object(参数1, ..., 参数N)

通常,这样一个 apply 方法返回的是伴生类的对象,比如:

class Account private (val id: Int, initialBalance: Double) {  
  private var balance = initialBalance
  ...
}

object Account {  
  def apply(initialBalance: Double) = new Account(newUniqueNumber(), initialBalance)
  ...
}

val acct = Account(1000.0) // 等效于 val acct = Account.apply(1000.0)  

在Scala中,类只能有一个超类,但可以实现任意数量的特质。

特质常用于mixin类中,而特质与类的唯一区别在于,特质的构造器没有参数。

xiehao

Read more posts by this author.