Scala 编程学习笔记1: 基本语法

这是最近学习scala编程的读书笔记。因为我是从c,python,java这些语言过来的,可能更关注和这些语言的差异。记录的也比较粗糙。

对我而言最重要的,就是scala是一种函数式语言的写法,以前不熟悉,需要多花时间去适应。

变量定义

变量定义 val 和 var: val 不变,var可变。

scalar有type inference 的能力。

优雅(?)语法

这个写法,叫做function literal:

 

args.foreach(arg => println(arg))

scala写起来可以很优雅。蒙特卡洛算法计算圆周率:

 

val count = spark.parallelize(1 to NUM_SAMPLES).map{i =>
  val x = Math.random()
  val y = Math.random()
  if (x*x + y*y < 1) 1 else 0
}.reduce(_ + _)
println("Pi is roughly " + 4.0 * count / NUM_SAMPLES)

遇到不懂语法时,也可以很让人抓狂:

 

asInstanceOf[Iterator[_ <: Product2[Any, Any]]]
...
val reducePartition: Iterator[T] => Option[T] = iter => {
...

@transient _rdd: RDD[_ <: Product2[K, V]],
...

一切皆函数

Scala里的数组是通过把索引放在圆括号里面访问的

 

for (i <- 0 to 2) 
    print(greetStrings(i))

这里 0 to 2 的to是个函数,这里其实是 (0).to(2)。

看到这里你应该明白为什么数组要通过()访问了吧,其实语义上,scala数组的取元素操作也是函数。

greetStrings(i)其实会转换成greetStrings.apply(i)。

 

greetStrings(i) = "吃了么?" 

//其实是

greetStrings.update(i, "吃了么?")

方法不应该有副作用是函数风格编程的一个很重要的理念。方法唯一的效果应该是计算并返回值。

数据类型和不可变

scalar 的List是不可变的。append 是不支持的。

scalar的可变list叫ListBuffer。

scalar 的元祖Tuple和List不同在于,Tuple的元素的类型可以是不同的。这点和Python就是完全不同的概念。

 

val pair = (99, "Luftballons") 
println(pair._1) 
println(pair._2)

要命在于Tuple是从1开始的,因为照顾Haskell程序员的习惯。

Scalar 的Set和Map有可变不可变之分。可变意味着”+”可以把新元素加入集,不可变意味着会返回新集。

如果想可变和不变都引用,又想简单写,有个办法:

import scala.collection.mutable

直接用Set则仍是默认不可变的,如果想Set是mutable的,写成mutable.Set

理解可变和不可变:

 

var set = Set("Apple", "Orange")
println(System.identityHashCode(set))
set += "Peach"
println(System.identityHashCode(set))  //different from above one

val mutableSet = scala.collection.mutable.Set("Apple", "Orange")
println(System.identityHashCode(mutableSet))
mutableSet += "Peach"
println(System.identityHashCode(mutableSet)) //The same as above one

scala: 崇尚val,不可变对象和没有副作用的方法。

类和对象

和java不同。java的默认访问级别是private,scala是public。

没有显式的return时,scala返回的是最后一个计算得来的值。

scala推荐把每个方法变成创建返回值的表达式。这是函数编程的思想。(最好不写return,这样更有函数式编程的腔调)

函数定义格式如图:

去掉方法体前面的等号时,它的结果类型将注定是Unit。

Scala比Java更面向对象(真是强迫症患者的语言)的是它没有静态成员。替代品: Scala有Singleton。用object做关键字。

Scala的Singleton(即object)可以和class重名(所谓伙伴。companion object 和companion class,必须在同一个源文件内。可相互互访私有成员)。你可以把Singleton作为静态方法之家。

单例不能带参数,因为没有new的机会。单例在第一次被访问时初始化。任何拥有main的单例对象都可以作为程序入口。

extends Application: 简单的无参数的单线程的程序可以用这个。其实就是省了个定义main函数的几下键盘输入。

基本类型和操作

scala 任何方法都可以是操作符

 

scala> s indexOf ('o', 5) // Scala调用了s.indexOf(o, 5) res1: Int = 8

Scala 的==和Java不同,不比较参考相等性。比较参考相等性用eq和ne。

富操作举例

  • -2.7 abs 2.7
  • (1.0 / 0) isInfinity true
  • 4 to 6 Range(4, 5, 6)

函数式对象

函数式对象: 没有任何可变状态的对象的类。

这段太精彩,抄录如下:

“不可变对象提供了若干强于可变对象的优点和一个潜在的缺点。首先,不可变对象常常比可变对象更具逻辑性,因为它们没有随着时间而变化的复杂的状态空间。其次,你可以很自由地传递不可变对象,而或许需要在把可变对象传递给其它代码之前,需要先建造个以防万一的副本。第三,没有机会能让两个同时访问不可变对象的线程破坏它合理构造的状态,因为根本没有线程可以改变不可变对象的状态。第四,不可变对象让哈希表键值更安全。比方说,如果可变对象在被放进了HashSet之后被改变,那么你下一次查找这个HashSet就找不到这个对象了。”

不可变对象唯一的缺点就是它们有时需要复制很大的对象图而可变对象的更新可以在原地发生。有些情况下这会变得难以快速完成而可能产生性能瓶颈。结果,要求库提供可变替代以使其更容易在大数据结构的中间改变一些元素也并非是一件稀奇的事情。例如,类StringBuilder是不可变的String的可变替代。”

require 方法,做类构造的先决条件。

 
class Rational(n: Int, d: Int) {
quire(d != 0) 
erride def toString = n +"/"+ d 
}

从构造器(主构造器之外的构造器):

 

class Rational(n: Int, d: Int) { 
  require(d != 0) 
  val numer: Int = n 
  val denom: Int = d
  def this(n: Int) = this(n, 1)

隐式转换,使用要谨慎。implicit 是告诉编译器在需要的时候隐式地做转换(我觉得这个虽易用,却邪恶)。

 

implicit def intToRational(x: Int) = new Rational(x)

Scala没有多少控制结构,仅有的包括if,while,for,try,match和函数调用。

Scala作为函数式语言,控制结构也产生值。

尽可能寻找使用val的机会。它们能让你的代码既容易阅读又容易重构。

while和do-while结构被称为“循环”,不是表达式,因为它们不产生有意义的结果,结果的类型是Unit(Scala 的 Unit 就是())。

Scala建议慎用while do-while。

 

while ((line = readLine()) != "") // 不起作用 
    println("Read: "+ line)

for的嵌套枚举下面的代码相当于两重for循环。

 

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

内建控制结构

mid-stream(流间)变量绑定

 

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)

注意for yield的语法:

for {子句} yield {循环体}

try except finally, 倒也和其他语言差不多。不过要注意都有返回值。

match: more than switch。后面再专门开一篇详细记述模式匹配。

关于scala,有一点可能会让c或者类c(java, python)的程序员疯掉,就是没有continue和break。

关于作用域,和Java的唯一不同是shadow:更内部的作用域可以定义和外部同样的变量名,有覆盖效果。



最后更新: 2015-05-28 22:51