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

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

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

其实现如下:

  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可能是什么时,它有些呆板吗?嗯

解决方法

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

  • while循环
  • tail-recursive,但保持与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

iter中的tailrecFind

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

while的核心与iter的核心没有太大区别:在足够的调用之后,JIT很可能会将它们带到相同的机器代码中。与进入循环的tailrecFind相比,进入iter的{​​{1}}的恒定开销要大一些。这里不可能有有意义的性能差异(事实上,由于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 ),不接受(Nil),不接受(谓词失败),但最后一次运行除外。

对我来说有点有趣,我们在检查::时接到了一个equals的电话:它可能仍然比Nil / isEmpty快,但是甚至在没有模式匹配并且针对nonEmpty使用显式eq / ne的情况下甚至更快。

我还注意到,针对Nil的模式匹配是有点反模式的IMO:在那一点上,使用虚拟方法分派几乎可以肯定更好,因为您基本上是在实现慢速vtable(如果您将常见情况放在首位,则可能具有准JIT的优势。

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

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

foldLeft

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res