先验知识
- 传名参数和传值参数:传值参数在函数调用之前表达式会被求值,传名参数在函数调用前表达式不会被求值,而是会被包裹成一个匿名函数作为函数参数传递下去,例如参数类型为无参函数的参数就是传名参数。
- function0是接受0个参数的函数类型。
- () => A和A是2种类型。() => A是个函数,A是个数据类型。
- => A 是 () => A的简写。
- lazy关键字:
- The difference between them is, that a val is executed when it is defined whereas a lazy val is executed when it is accessed the first time.
- 例:
val x
和 lazy val x
严格求值和非严格(惰性)求值
- 一个严格求值的函数总是对它的参数求值,即该参数会在函数被调用前求值。
- 给一个函数传递一个未求值的参数,在函数中引用的地方会被求值,即scala不会缓存未求值参数的求值结果。这是因为未求值的参数是一个匿名函数,每次引用相当于对该函数求值。可以用lazy关键字来显示地缓存这个值。
非严格求值函数
- scala中非严格求值的函数接收的参数是传名参数而非传值参数。
- scala中用() => A这种无参函数作为传名参数实现非严格求值函数。
- on: () => A表示参数on非严格求值,其实是一个接受0个参数的函数,通常一个表达式的未求值形式称为chunk
- 使用on()强制对on求值
一个非严格求值的例子
Stream[A] = Cons(h: () => A, t: () => Stream[A])
- cons函数接受2个非严格求值参数。即
() => A和() => Stream[A]
- 所以在apply中调用cons的时候,即as.head和apply(as.tail: _)这2个参数会被系统包装为chunk。即相当于cons(() => as.head,() => apply(as.tail: _))。因此这2个参数是传名参数,不会被求值所以递归不会发生。
- 如调用
apply(1,2,3),apply
只会被调用1次。返回值为Cons(() => 0,() => apply(2,3))。
def cons[A](hd: => A, tl: => Stream[A]) = {
lazy val head = hd
lazy val tail = tl
Cons(() => head, () => tail)
}
def empty[A]: Stream[A] = Empty
//初始化时仅仅执行一次
def apply[A](as: A*): Stream[A] = {
println("hell")
if (as.isEmpty) empty else cons(as.head,apply(as.tail: _*))
}
把函数的描述和求值分离
- 对于Stream,可以构建一个产生一系列元素的计算逻辑直到实际需要这些元素时才