编译
scalac HelloWorld.scala
scala HelloWorld
CLASSPATH 相关
通过环境变量或者执行参数
$SCALA_HOME
scala -cp
基础文法
scala 可以不写分号, 但是如果需要断行, 需要注意如
a LF + b 不行, 必须写成 a + NF b 或者 (a LF + b)
注释有两种, /* ... */ 和 // ..
if 语句语法是
if (COND) EXPR else EXPR
while 语法
while (COND) EXPR
for 语法是
for (PATTERN <- EXPR) EXPR
也可以用花括号
for {PATTERN <- EXPR} EXPR
可以有 guard
for (PATTERN <- EXPR if COND if COND...) EXPR
如
for (v <- 1 until 20 if v % 2 == 0 if v % 3 == 0) println(v)
还可以做模式匹配 destructuring
for (v <- (1 to 20) zip (20 to 1 by -1);
(x, y) = v;
if (x % 2 == 0) && (y % 3 == 0))
println(x, y)
import 和 package
scala 和 package 和 Java 类似, 依赖文件系统, 名称也类似 com.example.xxx
import 有几种格式
import users
import users.User
import users.{User, UserPref}
import users._ // 同 Java: import users.*
import users.{UserPref => UP} // Python: from users import UserPref as UP
import _root_.users._ // Rust: use ::users::*
import 可以在文件中任何地点出现, 局部作用域都可以.
默认导入
* scala._ (e.g. Boolean)
* java.lang._ (e.g. String)
* scala.Predef._ (e.g. assert/assume/require/ensuring)
声明变量
基本格式如
val IMMUTVAR [: TYPE] = INIT_VAL
var MUTVAR [: TYPE] = INIT_VAL
如
val a = if (true) 5 else 6 // 5
val b = a + 1 // 6
val succ = (x: Int) => x + a
val t: (Int, Int) = (2, 3) // 声明 Tuple, 函数也可以返回 Tuple
val t: (Int, Int) = 2 -> 3) // 另外的语法
t._1 // 返回第一个元素: 2
val (v1, v2) = t // 类似 Rust 的模式匹配解构
声明函数
基本语法是
def NAME(ARG: TYPE [= DEFAULT_VAL]): RET_TYPE = EXPR
其中 RET_TYPE 可以省略. 但是递归函数必须指明返回类型
如
def c1 = a + 1 // 完全无参数的函数
println(c1) // 调用函数
def c2() = a + 1 // 无参数的函数
println(c2) // 调用函数
println(c2()) // 也是调用函数
def fact(x: Int): Int = {
print(s"calling fact($x)")
if (x == 0) 1 else x * fact(x-1)
}
def sum(x: Int*): Int = (0 /: x)(_+_) // 变长参数
sum(1, 2, 3, 4) // 10
// Int* 事实上是 Seq[Int]
通常, 如果函数有副作用, 那么定义和调用都该用 c2() 的形式.
没副作用可以用 c1 的形式.
调用的时候可以类似 Python 指定传参名, 如
def foobar(x: String="Hello", y: String="World") = println(s"$x $y")
foobar() // Hello World
foobar("Hi") // Hi World
foobar(y="Tom") // Hello Tom
调用还可以用花括号而非圆括号, 无论是 def 函数还是 val 函数,
主要用在匿名函数的参数, 如
def invoke(x: => Unit) = x
invoke { println("Hello world" }
函数定义可以嵌套
函数不会自动 currying, 因此为了 currying 需要写
def add(x: Int)(y: Int) = x + y
它和一起定义 x, y 是不同的. 如果希望从这种定义中取得部分函数, 需要一个下划线
val add2 = add(2)_
之后
add2(6) // 8
函数类型是 args => ret, 如
def add(x: Int)(y: Int) = x + y // add 类型: Int => Int => Int
def foo(f: Int => Int => Int) = println(f(2)(3))
foo(add) // 5
上面的例子, Java 兼容的写法是 function2[Int, Function2[Int, Int]]
import scala.Function1
def bar(f: Function1[Int, Function1[Int, Int]]) = println(f(2)(3))
bar(add) // 5
传参的时候默认是按值传参, 但是也支持按名传参, 就是对传入值做 lazy 计算
如
var a = 0
def foobar0(x: Int) = { a = 2; println(x) }
foobar0(a) // 0
a = 0
def foobar1(x: =>Int) = { a = 2; println(x) }
foobar1(a) // 2
高阶函数
声明局部函数的格式如
(x: Int) => x+1
类型是
Int => Int
作为参数时, 可以推断出 x 的类型, 故不用写出类型
x => x * 2
也可以简写成
_ * 2
scala 和 haskell 不同, 不会自动 curry. 因此
(x: Int, y: Int) => x + y
的类型是 (Int, Int) => Int, 而
(x: Int) => (y: Int) => x + y
的类型是 Int => Int => Int
函数类型用在参数和返回值中, 如
def comp(f: Int=>Int, g: Int=>Int): Int=>Int = ((x: Int) => f(g(x)))
类型
Any 是所有类型的父类. 其子类包括 AnyVal 和 AnyRef.
AnyVal 如 Int, Float, Byte, Unit 等.
AnyRef 如 List, Option, 用户定义的类型等.
所有类型都是 Nothing 的父类. Nothing 没有实例.
所有 AnyRef 都是 Null 的父类, Null 只有一个实例 null.
基础类型有 Int, String, Double, Boolean 等.
声明类
声明语法是
class CLASS_NAME(CTOR_ARG: ARG_TYPE [= DEFAULT_VAL]...)
DECL*
}
DECL 包括方法 def, 成员 val 和 var, 类型代称 type, 如
class Foobar {
def foobar() = println("foobar")
private val = 24
type Output
}
构造函数传入的参数可以直接用, 如
class Circle(r: Double) {
def area = 3.14 * r * r
}
var c = new Circle(5.0)
c.area
如果希望将传入的参数保存到一个域, 可以直接在参数列表中声明
class Foobar(a: Int, val b: Int, var c: Int, private var d: Int)
var c = new Foobar(1, 2, 3, 4)
println(c.a) // Error: no field a in c
println(c.b) // 2
println(c.c) // 3
println(c.d) // Error: field d not accessible
c.b = 6 // Error: reassignment to val
c.c = 7 // ok
实例化语法如
new CLASS_NAME(ARGS)
可以把 new 完的结果赋给 val 或者 var
调用方法语法如 INSTANCE.METHOD(ARGS),
但可以省略变成 INSTANCE METHOD(ARGS)
如 (1::2::3::Nil) foreach(println)
这相当于把 method 看成 operator.
如果没有 ARGS, 普通情况下不能直接 INSTANCE METHOD, 需要
import scala.language.postfixOps
scala 中 method 名可以是特殊字符,
所以 2 + 3 实际上是 2.+(3), 3 * 5 实际上是 3.*(5)
这也就是算符重载的方法: def +(other: ...) ...
算符重载的优先级是 */% 大于 +- 大于 : 大于 =! 大于 ... 大于 普通标识符
可以有 getter 和 setter, 如
private var _NAME: TYPE
def NAME = _NAME
def NAME_=(ARG: TYPE): Unit = { // assignment }
和 Java 一样, scala 允许嵌套类, 但是创建不能像 Java 一样 new Outer.Inner
class Outer {
class Inner(val i: Int)
def newInner(i: Int) = new Inner(i)
}
val a = new Outer
val b = new Outer.Inner(5) // Error
val b = a.newInner(5) // Ok. b: Outer#Inner
case class
和普通的类不同, 类似数据类, 可以用来做模式匹配.
所有域都是 public 的 val. 所以默认 immutable, 并且按值比较.
声明语法如
case class CLASS_NAME(ARG: ARG_TYPE, ARG: ARG_TYPE...)
如
case class Person(name: String, age: Int, sex: String="male")
实例化不需要 new (原理是 case class 的 apply 函数负责对象构建)
CLASS_NAME(ARGS)
如
var jack = Person("Jack", 23)
var tom = Person(name="Tom", age=23)
可以复制, 如
var jackie = jack.copy(sex="female")
模式匹配
模式匹配的基本格式是
EXPR match {
ARM*
}
其中 ARM 的结构时
case PATTERN GUARD? => EXPR
如
val x = 23
x match {
case 23 => "twenty-tri"
case 20 => "twenty"
case _ => "others"
}
模式匹配必须是完备的, 否则会产生 *运行时错误*
var x = 5
x match {
case 23 => "twenty-tri"
case 20 => "twenty"
}
一个常见的设计方法是, 实现类似 Rust 的 enum 类型可以使用抽象类加上 case class,
当然 trait 也可以
abstract class Event
case class Move(x: Int=0, y: Int=0) extends Event
case class Quit() extends Event
case class Talk(s: String) extends Event
之后
def tellEvent1(e: Event) = {
e match {
case m: Move => println("Moving " + m)
case _: Quit => println("Quitting")
case _: Talk => println("Jabbering")
}
}
并且可以在 PATTERN 中对被 match 的 EXPR 做 destructuring, 如
def tellEvent2(e: Event) = {
e match {
case Move(x, y) => println(s"Moving with dx=$x, dy=$y")
case Quit() => println("Quitting")
case Talk(s) => println(s"He's talking like $s")
}
}
Pattern 也可以有 Guard
def tellEvent3(e: Event) = {
e match {
case Move(x, y) if x > 5 || y > 6 => println("Moving too fast")
case Talk(s) if s.length > 10 => println("Stop jabbering man")
}
}
上面的设计模式可以把抽象类或者 trait 加上 sealed, 要求所有子类都在同一文件中
这样就完全防止外部拓展的可能, 便于编译器检查
sealed trait Event
case class Move(x: Int=0, y: Int=0) extends Event
case class Quit() extends Event
case class Talk(s: String) extends Event
def tellEvent4(e: Event) = {
e match {
case Move(x, y) if x > 5 || y > 6 => println("Moving too fast")
case Talk(s) if s.length > 10 => println("Stop jabbering man")
}
}
object
相当于 singleton, 它们的类只有一个实例.
声明如
object OBJ_NAME {
DECL*
}
如
object Logger {
def log(a: String) = println(a)
}
Logger.log("Hello world")
如果 OBJ_NAME 和某个类的名称相同, 称这个对象是该类的 companion object.
companion 对象和类必须在同一文件中定义
通常的用途有
* 将 Java 的 static 方法放到 companion object 中.
这是不能通过 companion 类的对象来调用 static 方法.
* 通常把工厂方法放到 companion 对象中
如
object Cat {
def meow() = println("Meow~~")
def fromNothing() = Cat("God")
}
case class Cat(val name: String) {
import Cat._
def hello() = { println(s"Hello I'm a cat $name"); meow }
}
Cat.fromNothing().hello()
trait
包含域和方法的类型. 多个 trait 可以结合.
声明如
trait TRAIT_NAME {
DECL*
}
DECL 中域和方法可以有值或实现, 它们是默认值和默认实现
方法实现 trait 如
class CLASS_NAME(ARGS) [extends {SUPER|TRAIT} [with TRAIT*]]
其中如果重写默认实现或默认值, 需要用 override
override DECL
有一个概念就是 mixin.
通过一系列的 mixin traits, 可以直接构建一个类.
因为 trait 有数据和方法, 又有默认值和实现, 所以它克服了 interface 的缺点.
又因为 trait 更多地是语义的概念, 和域的内存排布没有直接联系,
所以克服了 (C++ 式) 多重继承的缺点.
为了表达一个对象需要有多个 trait, 可以用 compound type
trait Hello {
def hello(): Unit
}
trait Bye {
def bye(): Unit
}
def helloAndBye(v: Hello with Bye) = { v.hello; v.bye }
如果希望表达, 要用 trait A 的时候必须以形式 A with B 出现,
但是不想让 A 继承 B, 那么应该使用 self type, 形式是一个声明 this: B => 如
trait Sentence {
val str: String
}
trait Writer {
this: Sentence =>
def write() = { println(s"writing $str") }
}
泛型
类型参数传统是用 A 代表
泛型类如下, 泛型参数在类名后
abstract class Set[A] {
def contains(a: A): Boolean;
def add(a: A): Unit
}
泛型函数如下, 类型参数在函数名后
def externalAdd[A](xs: Set[A], x: A): A = { ... }
使用泛型的时候通常可以让编译器推断类型参数
scala 默认泛型也是 invariant 的, 也就是说 (is 表示 subclassof)
A is B 不能推出 T[A] is T[B], 也不能推出 T[B] is T[A]
不能把 List[Char] 当成 List[Int] 来用, 不然就可能向 List[Char] 中加入 Int 了
如果 A is B 可以推出 T[A] is T[B], 则称 T 的泛型是 covariant 的.
上面提到的 covariant 问题只有当允许向 List[Char] 中加入元素,
i.e. List 为 mutable 的时候才会出现
因此默认的 immutable.List 是满足 List[Char] is List[Int] 性质的.
为了让泛型变成 covariant, 只需在类型参数前缀 +, 如
SEALED abstract class List[+A] // scala.collection.immutable
或者更易理解的,
Cat is Animal
-> Factory[Cat] is Factory[Animal]
相反, 若 A is B 可以推出 T[B] is T[A], 那么称 T 的泛型是 contravarint 的.
给类型参数加前缀 - 来让泛型变成 contravarint 的, 如
abstract class Printer[-A] {
def print(v: A): Unit
}
或者更易理解的,
BinaryAST is AST
-> Visitor[AST] is Visitor[BinaryAST]
covariant 和 contravariant 的例子还有一个就是,
函数 T -> R 实际上是 Function1[-T, +R], i.e. (super T -> sub R) is (T -> R)
因为 (super T -> sub R) 也完成了 (T -> R) 的工作
泛型类型也可以加上限制, 如要求类型参数 T 是实际类型 A 的子类,
A 称为类型参数 T 的上界. 记为 T <: A 如
def Animal2List[A <: Animal](a: A): List[A] = List(a)
Animal2List(Cat("kitty")) // List[Cat] = List(Cat("kitty"))
要求 T 必须是 A 的父类, 则 A 是 T 的下界, 记为 T >: A
从形式化类型论的角度很容易区分 variance 和 bounding.
在 trait 和抽象类中, 允许有抽象类型如
trait Wrapper {
type T
val elem: T
}
允许抽象类型使用泛型操作如上下界
trait SeqWrapper extends Wrapper {
type U
type T <: Seq[U]
def length = elem.length
}
有时可以把 type parameter 变成抽象类型
for comprehension
comprehension 就类似 python 中的 [x*x for x in range(20)]
scala 基本的 comprehension 的格式如
for (ENUMERATORS) yield EXPR
comprehension 生成一个列表, 并非 iter. 如
for (x <- 1 to 10; if x % 3 == 0) yield x*x // Vector(9, 36, 81)
ENUMERATORS 可以包含以下元素, 分号隔开
* 局部 scope 的新变量, 如 x <- 1 to 10
* guards: 如 if x + y == 10
如
var pairs = for (i <- 1 to 10; j <- 1 to 10; if i + j == 10) yield (i, j)
如果不写 yield, 那么 for 的结果类型是 Unit, 通常这用来模拟普通语言的 for 循环
如
for (i <- 1 to 10) println(i)
implicit
函数可以有 implicit 参数. 这种参数不用显式传入, 而是 scala 去寻找然后自动传入
寻找的规则是
* 首先检查, 函数 call site 处所有用 implicit 前缀定义的符号
* 然后去 implicit 参数的类型的 companion object 中寻找
如
implicit val a = 5;
def foobar(implicit v: Int) = { println(v) }
foobar // 5
foobar(6) // 6
当然除了函数参数, 构造函数参数也可以是 implicit 的
implicit 必须和 currying 结合, 就是说不能
def foobar(x: Int, implicit y: Int)
而只能写
def foobar(x: Int)(implicit y: Int)
并且如果写成
def foobar(implicit x: Int, y: Int)
那么 x, y 都是 implicit 的
scala 可以支持隐式类型转换, 不过隐式类型转换经常导致问题所以需要手动打开
import scala.language.implicitConversions
从 S 到 T 的隐式类型转换是一个 implicit S => T 的函数
然后会在两种情况下执行隐式转换
* 在期望 T 类型的时候却看到一个 S 类型:
寻找 S => T
* e 是 S 类型, 但是 S 类型没有 m 字段, 但却是用了 m.e:
寻找 S => T, 而且 T 有 m 字段
如
implicit def double2int(x: Double): Int = x.intValue
def printInt(x: Int) = { println(s"printInt $x") }
printInt(3.5)
apply 和 unapply
类和对象可以实现 apply 方法, 它们的语义也很简单
class Foobar { def apply(s: String) = println(s"Hello $s") }
var t = new Foobar
t("Hi") // Hello Hi
而 unapply 主要是用来实现模式匹配的 destructuring,
相当于从 apply 的结果中抽取参数.
通常 unapply 的参数是 apply 的返回类型,
unapply 的返回类型是
* Bool: 看这个返回值是否是 apply 产生的
* Option[T], Option[(T1, T2...)]: 返回应用 apply 的参数
注意 case class 自动定义了 unapply, 所以如果想做自定义的 unapply, 不能用 case class.
常见操作
range 操作
val inclusive_range = 1 to 10 // Range.Inclusive(1, 2, 3, ..., 10)
val exclusive_range = 1 until 10 // Range(1, 2, 3, ..., 9)
批量赋值
val (x, y) = (5, 6)
println(s"x is $x and y is $y") // x is 5 and y is 6
随机数
import scala.util.Random
val x = Random.nextInt(10)
防御性编程
def doublePositive(n: Int): Int = {
require(n > 0)
n * 2 // logic
} ensuring(n => n >= 0 && n % 2 == 0)
Option
scala.Option[+A]
最合适的是把 Option 当成 collection 然后用 monad 来做
val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
也可以用模式匹配来做
name match {
None => println("Error")
Some(name) => println(name)
}
函数包括
getOrElse(v: A) -> A
容器
容器都在 scala.collection.immutable 和 scala.collection.mutable 中.
最常用的 List 就是 immutable.List.
容器的构造语法都统一的是
List(1, 2, 3), Seq(6, 7, 8), HashMap(1 -> "one", 2 -> "two")
scala 有一个容器继承树, 最根上是 Traversable, 然后是 Iterable
Traversable[+A] 提供的函数有如下
foreach
var a = List(1, 2, 3, 4, 5)
var b = Vector("a", "b", "c")
a foreach print // 12345
b foreach print // abc
++
++:
a ++ b // List[Any](1, 2 ...)
a ++: b // Vector[Any](1, 2 ...)
foldLeft
foldRight
/:
:\
reduceLeft
reduceRight
(a foldLeft 20)(_-_) // 5
(a foldRight 20)(_-_) // 5
(5 /: a)(_+_) // 20
(a :\ 5)(_+_) // 20
a reduceLeft (_-_) // -13
a reduceRight (_-_) // 3
sum
product
min
max
println(a.sum, a.product, a.min, a.max) // (15, 120, 1, 5)
tail
init
take
drop
head
last
find
slice
a.tail // List(2, 3, 4, 5)
a.init // List(1, 2, 3, 4)
a take 3 // List(1, 2, 3)
a drop 3 // List(4, 5)
a.head // 1
a.last // 5
a find (_ % 3 == 2) // Some(2)
a slice(2, 4) // List(2, 3)
filter
map
flatmap
a filter {x: Int => (x & 1) == 1} // List(1, 3, 5)
a map (_+1) // List(2, 3, 4, 5, 6)
a map {x: Int => List.fill(x)(x)} // List(List(1), List(2, 2), List(3, 3, 3) ...)
a flatMap {x: Int => List.fill(x)(x)} // List(1, 2, 2, 3, 3, 3 ...)
isEmpty
nonEmpty
size
a.isEmpty // false
a.nonEmpty // false
a.size // 5
exists
forall
count
a exists (_ % 2 == 2) // false
a forall (_ % 1 == 0) // true
a count (_ % 2 == 1) // 3
splitAt
partition
groupBy
a splitAt 2 // (List(1, 2), List(3, 4, 5))
a partition (_ % 2 == 1) // (List(1, 3, 5), List(2, 4))
a groupBy (_ % 3) // Map(2 -> List(2, 5), 1 -> List(1, 4), 0 -> List(3))