1. 严格函数、非严格函数、传值函数、传名函数
a. 严格函数:一个严格求值的函数总是对它的参数求值
b. 非严格函数:一个非严格求值的函数可以选择不对它的一个或多个参数求值,如if-else对条件参数是严格求值,对分支是非严格求值
c. 传值参数:(先对参数求值,再执行函数)函数调用参数之前参数(若包含表达式的话)会被求值
// 执行函数时会输出call strToInt
def strToInt(s: String) = {println("call strToInt");s.toInt}
// 传入的参数s需要计算表达式,计算表达式时会输出eval parameter expression
strToInt({println("eval parameter expression"); "123"})
// 执行顺序符合正常预期,先对参数的值进行计算,然后带入函数进行计算
d. 传名参数:(先执行函数,按需对参数求值)函数真正调用参数时才会对参数进行求值,传名函数不会被立刻求值,会自动包裹为匿名函数作为函数参数传递下去,注意传名函数不能是严格函数,如:()=>1
// 执行函数时会输出call strToInt,s: => String代表返回值为String的函数或代码
def strToInt(s: => String) = {println("call strToInt");s.toInt}
// 传入的参数s需要计算表达式,计算表达式时会输出eval parameter expression
strToInt({println("eval parameter expression"); "123"}) // 正确使用:传入代码块,strToInt会对代码块惰性求值
def test(t: String) :String = t; strToInt(test("123")) // 正确使用:传入等待计算的函数,strToInt也会对函数惰性求值
// 执行顺序特殊,先对函数进行计算,遇到参数s时,对s的值进行计算
// 错误的传名函数
strToInt(() => "123") // 由于() => "123"是严格函数,需要对函数进行立刻求值,这与参数类型s: => String相冲突,因此报错
strToInt((() => "123")()) // 但如果一定要传入严格函数,可以使用()进行包裹,后接(),等价于传入一个String类型值,可以规避报错,但不能惰性求值
2. 惰性求值:包含未求值的参数类型,不缓存结果,直接求值
a. 常规形式:参数列表中的传入参数必需为表达式、函数名,函数体中调用传入函数时必需带有括号()
def if2[A](cond: Boolean, onTrue: () => A, onFalse: () => A): A = if(cond) onTrue() else onFalse()
if2(true, () => "a", () => "b") // 得到结果a,因为调用函数if2之前传入函数值会被计算
if2(true, "a", "b") // 运行报错,类型不符,需要传入函数
b. 省略形式:参数列表中传入参数可以为表达式、值,函数体中调用传入函数时不带括号()
def if2[A](cond: Boolean, onTrue: => A, onFalse: => A): A = if(cond) onTrue else onFalse
if2(true, () => "a", () => "b") //得到结果() => String => <fiunction0>,因为只有在必要时才会对onTrue、onFalse计算, 奇怪的是自始至终都没有对() => "a", () => "b"进行计算,需要在末尾加()显示调用强制求值,才会得到最终结果
if2(true, "a", "b")
c. 直接求值:当参数值需要对对表达式求值才能得出时,可能会被计算多次
def if2[A](cond: Boolean, onTrue: => A, onFalse: => A):A= if(cond) {onTrue; onTrue} else onFalse //函数体中出现两次onTrue,这样会被计算两次
if2(true, {println("Calculated multiple times"); "a"}, "b")
d. 惰性求值:当不得不用到参数值时,即参数值第一次被引用的时候,才会对表达式求值,并缓存参数值
def if2[A](cond: Boolean, onTrue: => A, onFalse: => A): A =
{
lazy val temp = onTrue; // 设置一个惰性求值变量temp,对onTrue进行引用
if(cond) {temp ; temp } else onFalse // 第一次对temp进行引用,此时才会对onTrue进行求值,且只求值一次
}
if2(true, {println("Calculated once time"); "a"}, "b")
3. 自定义惰性Stream:定义一个列表,解析list-like参数,生成一个嵌套List的Stream
a. 直接求值:迭代解析head、tail生成嵌套的Stream
// 定义基本类型
sealed trait Stream[+A]
// 定义代表0的对象
case object Empty extends Stream[Nothing]
// 定义代表1-N的类型,使用传值函数,传入参数的值会在初始化类之前被计算出来
case class Cons[+A](hd: A, tl: Stream[A]) extends Stream[A]
// 定义Stream实现
object Stream{
// 调用代表0的对象
def empty[A]: Stream[A] = Empty
// 调用代表1-N的对象
def cons[A](hd: A, tl: Stream[A]):Stream[A] = {
Cons(hd, tl)
}
// 迭代生成Stream, A*是一个极其方便的变长参数列表
def apply[A](as: A*): Stream[A] = {
if (as.isEmpty) empty else cons(as.head, apply(as.tail: _*))
}
// 迭代取出Stream的元素,apply的逆操作
def unapply[A](stream: Stream[A]): Option[A] = stream match {
case Empty => None
case Cons(h,t) => Some(h)
}
}
// 测试Cons类型
val test = Cons(1, Empty)
// 测试Stream的emtpy接口
Stream.empty
// 测试Stream的cons接口
Stream.cons(1, Empty)
// 测试Stream的apply接口
val test = Stream.apply(1,2,3,4)
// 测试Stream的unapply接口
val temp = Stream.unapply(test)
b. 惰性求值:将head与tail转化为惰性求值的函数
// 定义基本类型
sealed trait Stream[+A]
// 定义代表0的对象
case object Empty extends Stream[Nothing]
// 定义代表1-N的类型
case class Cons[+A](h : () => A, t: () => Stream[A]) extends Stream[A]
// 定义Stream的实现
object Stream{
// 定义智能构造器,本质是一个方法,而不是构造函数,因此方法名用小写表示
// 改进原有的列表,使得hd和tl惰性求值,参数列表使用hd: => A, tl: => Stream[A]的好处在于即可以传入值,也可以传入函数
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {
// 要求hd和tl惰性求值
lazy val head = hd
lazy val tail = tl
// () => head和() => tail是将head、tail转化为函数传给cons
Cons(() => head, () => tail)
}
// 调用代表0的对象
def empty[A]: Stream[A] = Empty
// 迭代生成Stream
def apply[A](as: A*):Stream[A] = if(as.isEmpty) empty else cons(as.head, apply(as.tail: _*))
// 迭代取出Stream的元素,apply的逆操作
def unapply[A](stream: Stream[A]):Option[A] = stream match{
case Empty => None
// 记住这里必需使用h(),否则Option将会返回Option[function0],类型错误
case Cons(h,t) => Some(h())
}
}
// 测试Cons类型
val test = Cons(() => 1, () => Empty)
// 测试Stream的emtpy接口
Stream.empty
// 测试Stream的cons接口
Stream.cons(1, Empty)
// 测试Stream的apply接口
val test = Stream.apply(1,2,3,4)
// 测试Stream的unapply接口
val temp = Stream.unapply(test)
4. 无限流与共递归
a. 定义无限流:通过Stream.cons(A, Stream[A])构造无限流,通过递归构造无限流,递归是收敛的
// 定义一个常数无限流
val ones: Stream[Int] = Stream.cons(1, ones) // 常量形式:恰好不需要参数的情况
def ones: Stream[Int] = Stream.cons(1, ones) // 函数形式
// 定义一个递增无限流
val from: Int => Stream[Int] = (n:Int) => Stream.cons(n, from(n+1)) // 变量形式:返回值应为函数Int => Stream[Int]
def from(n:Int):Stream[Int] = Stream.cons(n, from(n + 1)) // 函数形式:返回值为Stream[Int]
// 定义斐波那契数列Fibonacci
val fib:(Int,Int) => Stream[Int] = (pre:Int, cur:Int) => Stream.cons(pre, fib(cur, pre + cur)) // 变量形式:返回值应为(Int, Int) => Stream[Int]
def fib(pre:Int, cur:Int): Stream[Int] = Stream.cons(pre, fib(cur, pre + cur)) //函数形式:返回值为Stream[Int]
// 如何定义一个递增质数序列?
b. 定义共递归:共递归是递归的对偶,相对于递归,共递归是发散的,接受一个初始状态,以及一个在生成的Stream中用于产生下一状态和下一个值的函数
// 共递归描述:z为初始状态,f接受初始状态,返回下一个值a和下一个状态s
def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] =
f(z) match {
case None => Stream.Empty
case Some((a, s)) => Stream.cons(a, unfold(s)(f))
}
// 常数共递归
val ones: Stream[Int] = unfold(1)(a => Some(1, a))
def ones:Stream[Int] = unfold(1)(a => Some(1, a))
// 递增共递归
val from : Int => Stream[Int] = (n :Int) => unfold(n)(a => Some((a, a+1)))
def from(n: Int): Stream[Int] = unfold(n)(a => Some((a, a+1)))
// 定义斐波那契数列Fibonacci,传入元组时无法自动解析,需要根据case进行模式匹配,或者使用单个变量的下标索引完成解析
val fib:(Int, Int) => Stream[Int] = (pre:Int, cur:Int) => unfold((pre, cur))(a => Some(a._2, (a._2, a._1+a._2)))
def fib(pre:Int, cur:Int):Stream[Int] = unfold((pre, cur))(a => Some(a._2, (a._2, a._1 + a._2)))