【scala函数式编程】严格求值和惰性求值

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)))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼摆摆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值