为什么Scala使用一会而不是使用`find`进行递归

托比

只是好奇为什么Scala作者findLists上实现时没有使用递归甚至模式匹配

它们的实现如下所示:

  override final def find(p: A => Boolean): Option[A] = {
    var these: List[A] = this
    while (!these.isEmpty) {
      if (p(these.head)) return Some(these.head)
      these = these.tail
    }
    None
  }

使用whileheadtail他们可以为带有递归的“ scala-esq”做些事情吗?

  @tailrec
  def find(p: A => Boolean): Option[A] = {
    this match {
      case Nil                     => None
      case head :: tail if p(head) => Some(head)
      case elements                => find(p, elements.tail)
    }
  }

不能因为尾部调用优化而可以吗?它以某种方式更有效,我想念它吗?可能仅仅是作者的喜好和风格吗?什么时候有些僵化,什么时候A可以呢?

列维·拉姆西(Levi Ramsey)

快速实验(使用Scala 2.13.2)。三种候选实现是:

  • while循环
  • 尾递归,但保持与while版本相同的逻辑
  • 具有模式匹配的尾递归

我在适当的地方修改了逻辑,以减少对编译器优化的依赖(nonEmpty相对于!isEmpty显式保存,these.head因此不会被调用两次)。

  import scala.annotation.tailrec
  
  object ListFindComparison {
    def whileFind[A](lst: List[A])(p: A => Boolean): Option[A] = { 
      var these: List[A] = lst 
      while (these.nonEmpty) {
        val h = these.head

        if (p(h)) return Some(h)
        else these = these.tail
      }   
      None
    }
  
    def tailrecFind[A](lst: List[A])(p: A => Boolean): Option[A] = { 
      @tailrec
      def iter(these: List[A]): Option[A] =
        if (these.nonEmpty) {
          val h = these.head
          if (p(h)) Some(h)
          else iter(these.tail)
        } else None
  
      iter(lst)
    }
  
    def tailRecPM[A](lst: List[A])(p: A => Boolean): Option[A] = { 
      @tailrec
      def iter(these: List[A]): Option[A] =
        these match {
          case Nil => None
          case head :: tail if p(head) => Some(head)
          case _ => iter(these.tail)
        }   
  
      iter(lst)
    }
  }

在检查字节码(使用:javap ListFindComparison$)时,我们看到

对于whileFind,发出的代码很简单

Code:
   0: aload_1
   1: astore_3
   2: aload_3
   3: invokevirtual #25                 // Method scala/collection/immutable/List.nonEmpty:()Z
   6: ifeq          50
   9: aload_3
  10: invokevirtual #29                 // Method scala/collection/immutable/List.head:()Ljava/lang/Object;
  13: astore        4
  15: aload_2
  16: aload         4
  18: invokeinterface #35,  2           // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  23: invokestatic  #41                 // Method scala/runtime/BoxesRunTime.unboxToBoolean:(Ljava/lang/Object;)Z
  26: ifeq          39
  29: new           #43                 // class scala/Some
  32: dup
  33: aload         4
  35: invokespecial #46                 // Method scala/Some."<init>":(Ljava/lang/Object;)V
  38: areturn
  39: aload_3
  40: invokevirtual #49                 // Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
  43: checkcast     #21                 // class scala/collection/immutable/List
  46: astore_3
  47: goto          2
  50: getstatic     #54                 // Field scala/None$.MODULE$:Lscala/None$;
  53: areturn

尾递归发现基本相同:

aload_0
aload_1
aload_2
invokespecial   // call the appropriate (private) iter methods
areturn

itertailrecFind

Code:
   0: aload_1
   1: invokevirtual #25                 // Method scala/collection/immutable/List.nonEmpty:()Z
   4: ifeq          53
   7: aload_1
   8: invokevirtual #29                 // Method scala/collection/immutable/List.head:()Ljava/lang/Object;
  11: astore        4
  13: aload_2
  14: aload         4
  16: invokeinterface #35,  2           // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  21: invokestatic  #41                 // Method scala/runtime/BoxesRunTime.unboxToBoolean:(Ljava/lang/Object;)Z
  24: ifeq          39
  27: new           #43                 // class scala/Some
  30: dup
  31: aload         4
  33: invokespecial #46                 // Method scala/Some."<init>":(Ljava/lang/Object;)V
  36: goto          50
  39: aload_1
  40: invokevirtual #49                 // Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
  43: checkcast     #21                 // class scala/collection/immutable/List
  46: astore_1
  47: goto          0
  50: goto          56
  53: getstatic     #54                 // Field scala/None$.MODULE$:Lscala/None$;
  56: areturn

whileand的核心之间没有太大的区别iter:在足够的调用之后,JIT很可能会将它们带到相同的机器代码中。tailrecFind进入循环iter相比whileFind,进入的恒定开销要大一些这里不可能存在有意义的性能差异(事实上,由于while将语言定义保留while为一点点,因此只要有谓词通过,尾部递归调用一个块的库函数就可以了)。

iter与模式匹配有很大的不同:

Code:
   0: aload_1
   1: astore        5
   3: getstatic     #77                 // Field scala/collection/immutable/Nil$.MODULE$:Lscala/collection/immutable/Nil$;
   6: aload         5
   8: invokevirtual #80                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
  11: ifeq          22
  14: getstatic     #54                 // Field scala/None$.MODULE$:Lscala/None$;
  17: astore        4
  19: goto          92
  22: goto          25
  25: aload         5
  27: instanceof    #82                 // class scala/collection/immutable/$colon$colon
  30: ifeq          78
  33: aload         5
  35: checkcast     #82                 // class scala/collection/immutable/$colon$colon
  38: astore        6
  40: aload         6
  42: invokevirtual #83                 // Method scala/collection/immutable/$colon$colon.head:()Ljava/lang/Object;
  45: astore        7
  47: aload_2
  48: aload         7
  50: invokeinterface #35,  2           // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
  55: invokestatic  #41                 // Method scala/runtime/BoxesRunTime.unboxToBoolean:(Ljava/lang/Object;)Z
  58: ifeq          75
  61: new           #43                 // class scala/Some
  64: dup
  65: aload         7
  67: invokespecial #46                 // Method scala/Some."<init>":(Ljava/lang/Object;)V
  70: astore        4
  72: goto          92
  75: goto          81
  78: goto          81
  81: aload_1
  82: invokevirtual #49                 // Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
  85: checkcast     #21                 // class scala/collection/immutable/List
  88: astore_1
  89: goto          0
  92: aload         4
  94: areturn

这几乎不可能与没有模式匹配的版本相提并论(尽管公平地说,对于预测器而言,分支实际上将非常容易:未采用(not- Nil),未采用(::),未- (谓词失败),但最后一次运行除外。

对我来说有点有趣,我们equals在检查时会打电话给Nil它:它可能仍然比isEmpty/更快nonEmpty,但是如果没有模式匹配并且带有显式eq/ne反对,它甚至会更快Nil

我还注意到,针对的模式匹配this是有点反模式的IMO:在这一点上,使用虚拟方法分派几乎可以肯定更好,因为您基本上是在实现慢速的vtable(它确实有可能预先如果您将普通情况放在首位,就可以这样做)。

如果您真的很在意性能,我会尽量避免模式匹配。

PS:我还没有分析简单的foldLeft解决方案:

lst.foldLeft(None) { (acc, v) =>
  acc.orElse {
    if (p(v)) Some(v)
    else None
  }
}

但是,由于这不会短路,因此我怀疑它不会一直击败任何一个候选者,即使在最后一个元素之前没有匹配的情况下,它甚至也可能没有匹配模式匹配的版本。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Erlang:为什么使用递归+反向而不是map来对List进行操作最快?

来自分类Dev

为什么在使用主要构造函数进行继承时,Scala子类会创建字段的副本?

来自分类Dev

为什么GCC会定义一元运算符'&&'而不是仅使用'&'?

来自分类Dev

为什么使用 GPU 而不是 CPU 时 tensorflow 会变慢?

来自分类Dev

为什么WebRTC使用候选对而不是一个IP +端口对进行双向通信

来自分类Dev

为什么 Flink 使用 Pushgateway 而不是 Prometheus 通常的 pull 模型进行一般指标收集?

来自分类Dev

为什么在 Scala 中使用 foreach 而不是 onSuccess 回调?

来自分类Dev

为什么该程序使用gcc而不是g ++进行编译?

来自分类Dev

为什么我用getattr()而不是__dict __ []进行无限递归?

来自分类Dev

使用递归Scala进行素数分解

来自分类Dev

为什么即使我没有使用 /S 参数,Forfiles 也会递归?

来自分类Dev

为什么Haskell使用->而不是=?

来自分类Dev

为什么 iloc 使用 [] 而不是 ()?

来自分类Dev

为什么在递归笛卡尔积函数中使用append-map而不是map?

来自分类Dev

为什么不是尾递归?

来自分类Dev

什么是UFR II打印机驱动程序?为什么我会优先使用它而不是另一种类型?

来自分类Dev

可以在scala中使用一会儿循环并产生收益

来自分类Dev

使用递归类型的基本Scala反射代码无法编译。为什么呢 如何解决?

来自分类Dev

使用惰性val的Scala线性化,为什么要无限递归?

来自分类Dev

使用惰性val的Scala线性化,为什么要无限递归?

来自分类Dev

为什么Scala使用反向shebang(!#)而不是仅将解释器设置为scala

来自分类Dev

使用一会儿(x)

来自分类Dev

使用一会儿(x)

来自分类Dev

为什么在使用DisplayPort菊花链连接时,我的两个Dell显示器之一会意外进入省电模式?

来自分类Dev

在同一会话中进行测试时,Tensorflow是否使用最佳权重或最新权重?

来自分类Dev

为什么我的Javascript动画过一会后变慢

来自分类Dev

如果使用setter方法,为什么irb会回显赋值的右侧而不是返回值?

来自分类Dev

为什么使用小书签时Chrome会更新正文而不是textarea值?

来自分类Dev

为什么使用默认构造函数“ {}”而不是“ = default”会导致性能差异?

Related 相关文章

  1. 1

    Erlang:为什么使用递归+反向而不是map来对List进行操作最快?

  2. 2

    为什么在使用主要构造函数进行继承时,Scala子类会创建字段的副本?

  3. 3

    为什么GCC会定义一元运算符'&&'而不是仅使用'&'?

  4. 4

    为什么使用 GPU 而不是 CPU 时 tensorflow 会变慢?

  5. 5

    为什么WebRTC使用候选对而不是一个IP +端口对进行双向通信

  6. 6

    为什么 Flink 使用 Pushgateway 而不是 Prometheus 通常的 pull 模型进行一般指标收集?

  7. 7

    为什么在 Scala 中使用 foreach 而不是 onSuccess 回调?

  8. 8

    为什么该程序使用gcc而不是g ++进行编译?

  9. 9

    为什么我用getattr()而不是__dict __ []进行无限递归?

  10. 10

    使用递归Scala进行素数分解

  11. 11

    为什么即使我没有使用 /S 参数,Forfiles 也会递归?

  12. 12

    为什么Haskell使用->而不是=?

  13. 13

    为什么 iloc 使用 [] 而不是 ()?

  14. 14

    为什么在递归笛卡尔积函数中使用append-map而不是map?

  15. 15

    为什么不是尾递归?

  16. 16

    什么是UFR II打印机驱动程序?为什么我会优先使用它而不是另一种类型?

  17. 17

    可以在scala中使用一会儿循环并产生收益

  18. 18

    使用递归类型的基本Scala反射代码无法编译。为什么呢 如何解决?

  19. 19

    使用惰性val的Scala线性化,为什么要无限递归?

  20. 20

    使用惰性val的Scala线性化,为什么要无限递归?

  21. 21

    为什么Scala使用反向shebang(!#)而不是仅将解释器设置为scala

  22. 22

    使用一会儿(x)

  23. 23

    使用一会儿(x)

  24. 24

    为什么在使用DisplayPort菊花链连接时,我的两个Dell显示器之一会意外进入省电模式?

  25. 25

    在同一会话中进行测试时,Tensorflow是否使用最佳权重或最新权重?

  26. 26

    为什么我的Javascript动画过一会后变慢

  27. 27

    如果使用setter方法,为什么irb会回显赋值的右侧而不是返回值?

  28. 28

    为什么使用小书签时Chrome会更新正文而不是textarea值?

  29. 29

    为什么使用默认构造函数“ {}”而不是“ = default”会导致性能差异?

热门标签

归档