美文网首页
Chapter 5: laziness 严格求值 (gold_a

Chapter 5: laziness 严格求值 (gold_a

作者: 胖达_4b7e | 来源:发表于2019-05-20 23:23 被阅读0次

    Scala 函数式编程 https://github.com/fpinscala/fpinscala](https://github.com/fpinscala/fpinscala)

    非严格non-strict 求值

    非严格求值 : 函数属性

    非严格求值的函数是说 这个函数可以选择不对它的入参求值.

    这种参数叫 by name 参数, (普通的叫 by value)

    只有少数语言支持 非严格求值, Java中的stream 就是非严格求值 , 不会频繁产生用完就可以扔的中间值

    例子:

      /**
        * 
        * @param cond 条件
        * @param onTrue 一个无参的函数
        * @param onFalse 一个无参的函数
        * @tparam A
        * @return
        */
      def  ifMy[A](cond:Boolean,onTrue:()=>A,onFalse:()=>A):A={
        if (cond)
          onTrue()// 这时才调用函数求值
        else 
          onFalse()//这时才调用函数求值
      }
    

    使用

      ifMy(
          12<22,
          ()=>println("true"),
          ()=>println("false")
        )
    

    一个表达式 未求值的形式 称为 thunk
    比如:
    ()=>println("true") 就是thunk

    scala对此提供了专门的语法

    函数声明入参时: onTrue:()=>A 简写为 onTrue: =>A 把括号换成空格
    调用时: ()=>println("true") 简写为 println("true"), 不再传无参函数 而是表达式, 会自动包装成thunk
    上面的函数声明就可以变成

      def  ifMy2[A](cond:Boolean,onTrue: =>A,onFalse: =>A):A={
        if (cond)
          onTrue// 括号没了
        else
          onFalse//括号没了
        }
    

    调用变成

       ifMy2(
          12<22,// 表达式, 但是传入的时候不会计算, 真的用到时才会计算
          println("true"),
          println("false")
        )
    

    用lazy 改进 :缓存

    参数求值的结果不会被缓存, 如

     def pair(i: => Int): (Int, Int) = {
        (i, i)
      }
    

    调用

    pair { 
          println("hi"); 
          1 + 41 
        }
    

    这样hi会打印2次, 每次用i都会重新计算
    如果不想每次都算一次, 得用lazy,
    lazy 的值的意思是, 真的用到时计算它的赋值, 之后都不用计算了
    原来函数改成

      def pair2(i: => Int): (Int, Int) = {
        lazy val j = i // 这样不会求值
        (
          j,// 第一次使用j时会求值
          j // 已经计算过了,用缓存
        )
      }
    

    惰性列表Stream

    Stream简单定义

    sealed trait Stream[+A]
    case object Empty extends Stream[Nothing]
    case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
    

    对比List

    sealed trait List[+A]
    case object Nil extends List[Nothing] 
    case class Cons[+A](head: A, tail: List[A]) extends List[A]
    

    看起来几乎一样, 除了 构造器接受 显式thunk, 这样传head和tail的时候不会求值,用()触发, 比如head() 这样才会强制求值

    但是和前文说的那样, 这样不能缓存值, 再次需要取head ,再调用head() 会再次计算
    用lazy写一个可缓存的, 更加智能地 构造Cons的函数

    //注意: 这个cons的入参可以直接写普通的表达式, 但是不会马上被求值
      def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {
        lazy val head = hd// lazy 转了一道
        lazy val tail = tl
        Cons(() => head, () => tail)// 缓存在head tail里面了
      }
    

    Stream(2,12,39) 这样来创建Stream时 调用的apply方法:

    def apply[A](as: A*): Stream[A] =
        if (as.isEmpty) 
          empty 
        else 
          cons(as.head, apply(as.tail: _*)) // 都不会强制求值, 直到第一次用到
    

    为了尾递归, 常用reverse

      def toList: List[A] = {
        @annotation.tailrec
        def go(s: Stream[A], acc: List[A]): List[A] = s match {
          case Cons(h,t) => go(t(), h() :: acc)
          case _ => acc
        }
        go(this, Nil).reverse
      }
    

    reverse的实现是如下, 只有n的时间复杂度

      override def reverse: List[A] = {
        var result: List[A] = Nil
        var these = this
        while (!these.isEmpty) {
          result = these.head :: result
          these = these.tail
        }
        result
      }
    
    

    分离: 表达式的描述 和 表达式的计算

    惰性求值 让我可以分离 表达式的描述 和 它的计算
    因此可以写一个更大的表达式, 但是只计算其中的一部分
    比如

    def foldRight[B](z: => B)(f: (A, => B) => B): B =
    this match {
    case Some((h, t)) => f(h, t.foldRight(z)(f))
    case None => z
    }
    

    注意 f 这个函数的第二个入参类型是=> Bnon-strict, 就是传进去时不会被计算, 在f里面真的用到的了才会计算
    第二个入参是 t.foldRight(z)(f) 如果根本用不到的话, foldRight 马上就结束了, 不会再调用foldRight 来递归
    而且, 因为是by-name入参,不会去计算, 这里即便递归多少次也不会栈溢出

    使用的例子

      def exists(p: A => Boolean): Boolean = {
    
        /**
          *
          * @param a Stream元素
          * @param z 是惰性求值
          * @return
          */
        def f  (a:A, z: =>Boolean) : Boolean={
           p(a) || z// 或的第一个是真 后一个就不会计算
        }
    
        foldRight(false)(f)
      }
    

    map:

      def map[B](g: A => B): Stream[B] ={
        def f [B](a:A,z: => Stream[B]) : Stream[B]={
         cons(g(a),z)
        }
        foldRight(empty[B])(f)
      }
    

    filter:

      def filter(g: A => Boolean): Stream[A] ={
        def f (a:A,z: => Stream[A]) : Stream[A]={
          if (g(a)){
            cons(a,z)
          }else{
            z
          }
        }
        foldRight(empty[A])(f)
      }
    

    Stream(1,2,3,4).map(_ + 10).filter(_ % 2 == 0)计算过程:

    cons(1+10, Stream(2,3,4).map(_ + 10)).filter(_ % 2 == 0)
    Stream(2,3,4).map(_ + 10).filter(_ % 2 == 0)
    cons(12, Stream(3,4).map(_ + 10).filter(_ % 2 == 0))

    Notice we do not fully instantiate the intermediate stream

    无限流

    val ones: Stream[Int] = cons(1, ones)

    scala> ones.take(5).toList
    res0: List[Int] = List(1, 1, 1, 1, 1)
    scala> ones.exists(_ % 2 != 0)
    res1: Boolean = true
    

    斐波那契数列 0 1 1 2 3 5 8

      def fibs():Stream[Int]={
        
        def help(a:Int,b:Int):Stream[Int]={
          cons(a,cons(b,help(a,b)))
        }
        help(0,1)
      }
    

    通用的

      /**
        * 用初始值和生产函数 生产可能无限的Stream[A]
        * @param z 初始值
        * @param f 用初始值 产生下一个stream元素和下一个初始值
        * @return
        */
      def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] =
        f(z) match {
          case Some((h,s)) => cons(h, unfold(s)(f))
          case None => empty
        }
    

    相关文章

      网友评论

          本文标题:Chapter 5: laziness 严格求值 (gold_a

          本文链接:https://www.haomeiwen.com/subject/avfgaqtx.html