Scala 是构建在 JVM 上的静态类型的脚本语言,而脚本语言总是会有些约定来增强灵活性。灵活性可以让掌握了它的人如鱼得水,也会让初学者不知所措。比如说 Scala 为配合 DSL 在方法调用时有这么一条约定:
1) 在明确了方法调用的接收者的情况下,若方法只有一个参数时,调用的时候就可以省略点及括号。如 “0 to 2”,实际完整调用是 “0.to(2)”。但 “println(2)” 不能写成 “println 10“”,因为未写出方法调用的接收者 Console,所以可以写成 “Console println 10”.
2) 用括号传递给变量(对象)一个或多个参数时,Scala 会把它转换成对 apply 方法的调用; balance(x,y) => balance.apply(x,y)
3) 与此相似的,当对带有括号并包括一到若干参数的进行赋值时,编译器将使用对象的 update 方法对“括号里的参数和等号右边的值”执行调用。 balance() = x => balance.update(x)
4) 函数定义简式
def f() { return .. }
|
调用:f, f()皆可
|
始终返回:Unit
|
def f() = ...
|
调用:f, f()皆可
|
返回Unit或者值
|
def f = ...
|
调用:f
|
返回Unit或者值
|
5) 偏函数 用下划线代替1+个参数的函数叫偏函数(partially applied function)
val p0 = sum _ // 正确
val p1 = sum(10,_,20) // 错误
val p2 = sum(10,_:Int,20) // 正确
val p3 = sum(_:Int,100,_:Int)
p0(1,2,3) // 6
p2(100) // 130
p3(10,1) // 111
或者:
(sum _)(1 2 3) // 6
(sum(1,_:Int,3))(2) // 6
(sum(_:Int,2,_:Int))(1,3) // 6
从上面可以看出,partial函数是一个正常函数中抽出来的一部分(参数不全),故称为partial函数。
6) 匿名参数
((i:Int, j:Int) => i+j)(3, 4) // 7 可以写成 ((_:Int) + (_:Int))(3,4) // 7 注意:括号(_:Int)括号是必须的
参数_在最后则可以省略,例如
1 to 5 map (10*) 1 to 5 foreach println
7) Curry化
例如:
def sum(a:Int, b:Int) = { a + b } // sum(1, 2) = 3
Curry化后:
def sum(a:Int)(b:Int) = { a + b } // sum(1)(2) = 3
或者:
def sum(a:Int) = { (b:Int)=> a + b } // sum(1)(2) = 3
// 调用方式二:val t1 = sum(10); val t2 = t1(20)
8) Iterator不属于集合类型,只是逐个存取集合中元素的方法,一次性的消费
使用while
val it = Iterator(1,3,5,7) 或者 val it = List(1,3,5,7).iterator
while(it.hasNext) println(it.next)
使用for
for(e<- Iterator(1,3,5,7)) println(e)
使用foreach
Iterator(1,3,5,7) foreach println
Iterator也可以使用map的方法:
Iterator(1,3,5,7) map (10*) toList // List(10, 30, 50, 70)
Iterator(1,3,5,7) dropWhile (5>) toList // List(5,7)
9) 尾递归
定义:函数尾(最后一条语句)是递归调用的函数。
tail-recursive会被优化成循环,所以没有堆栈溢出的问题。
线性递归的阶乘:
def nn1(n:Int):BigInt = if (n==0) 1 else nn1(n-1)*n
println(nn1(1000)) // 4023...000
println(nn1(10000)) // 崩溃:(
尾递归的阶乘:
def nn2(n:Int, rt:BigInt):BigInt = if (n==0) rt else nn2(n-1, rt*n)
println(nn2(1000,1)) // 40...8896
println(nn2(10000,1)) // 2846...000
10) Parallel Collection
(1 to 10).par foreach println
11) 协变和逆变(co-|contra-)variance
概念
使用“+”“-”差异标记
trait Queue[T] {} => 非变
trait Queue[+T] {} => 协变
如果S extends A (S为子类型,A为父类型),
则Queue[S]为子类型,Queue[A]为父类型
S <: A => Queue[S] <: Queue[A]
trait Queue[-T] {} => 逆变
如果S extends A (S为子类型,A为父类型)
则Queue[S]为父类型,Queue[A]为子类型,和协变互逆
S <: A => Queue[S] >: Queue[A]
-A是A的子集,叫逆变
+B是B的超集,叫协变
类型上下界
<%
foo[T <% Ordered[T]](...)
关系较弱:T能够隐式转换为Ordered[T]
<:
foo[T <: Ordered[T]](...)
关系较强:T必须是Ordered[T]的子类型,即T的类型范围小于Ordered[T],Ordered[T]为上界
>:
foo[T >: A](...)
关系较强:T必须是A的父类型,即Tde类型范围大于A,A为下界
协变、逆变结合上下界
例子1:
trait c1[+T] {
def m[K >: T](x:K) = x }
trait c1[-T] {
def m[K <: T](x:K) = x }
object c2 extends c1[Int]
c2.m(3) // 3
c2.m(3.0) // 3.0
c2.m("abc") // “abc"
object c2 extends c1[Int]
c2.m(3) // 3
c2.m(3.0) // 报错
c2.m("abc") // 报错
例子2:
// 非变
case class T1[T](e:T)
val v1:T1[java.lang.Integer] = new T1(100)
val v2:T1[java.lang.Integer] = v1
v2.e // 100
val v3:T1[java.lang.Number] = v1 // 报错
// 协变
case class T1[+T](e:T)
val v1:T1[java.lang.Integer] = new T1(100)
val v2:T1[java.lang.Integer] = v1
v2.e // 100
val v3:T1[java.lang.Number] = v1 // 合法
v3.e // 100
val v4:T1[java.lang.Integer] = v3 //非法
// 逆变
class T1[-T](e:T)
val v1:T1[java.lang.Number] = new T1(100)
val v2:T1[java.lang.Number] = v1
val v3:T1[java.lang.Integer] = v1 // 合法
val v4:T1[java.lang.Number] = v3 // 非法