为什么 scalac 会生成附加/包装闭包

问题描述 投票:0回答:1

首先。考虑以下代码

scala> val fail = (x: Any) => { throw new RuntimeException }
fail: Any => Nothing = <function1>

scala> List(1).foreach(fail)
java.lang.RuntimeException
    at $anonfun$1.apply(<console>:7)
    at $anonfun$1.apply(<console>:7)
    at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:59)

在 foreach 和异常之间还有额外的 anonfun 。第一个应该是

fail
本身的值(类 Function1[] 的对象),但是第二个来自哪里?

foreach
签名采用此功能:

def foreach[U](f: A => U): Unit 

那么,第二个的目的是什么?

其次,考虑以下代码:

scala> def outer() {
     |   def innerFail(x: Any) = { throw new RuntimeException("inner fail") }
     | 
     |   Set(1) foreach innerFail
     | }
outer: ()Unit

scala> outer()
java.lang.RuntimeException: inner fail
    at .innerFail$1(<console>:8)
    at $anonfun$outer$1.apply(<console>:10)
    at $anonfun$outer$1.apply(<console>:10)
    at scala.collection.immutable.Set$Set1.foreach(Set.scala:86)

还有两个额外的anonfuns...他们真的需要吗? :-E

scala closures
1个回答
4
投票

让我们看一下字节码。

object ExtraClosure {
  val fail = (x: Any) => { throw new RuntimeException }
  List(1).foreach(fail)
}

我们发现,在(单个)匿名函数内部:

public final scala.runtime.Nothing$ apply(java.lang.Object);
  Code:
   0:   new #15; //class java/lang/RuntimeException
   3:   dup
   4:   invokespecial   #19; //Method java/lang/RuntimeException."<init>":()V
   7:   athrow

public final java.lang.Object apply(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   invokevirtual   #27; //Method apply:(Ljava/lang/Object;)Lscala/runtime/Nothing$;
   5:   athrow

所以这实际上并不是一个额外的闭包。 我们有一个方法重载了两个不同的返回值(这对于 JVM 来说完全没问题,因为它将所有参数的类型视为函数签名的一部分)。 函数是通用的,所以它必须接受对象返回,但是您编写的代码专门返回

Nothing
,它还创建了一个返回您期望的类型的方法。

解决这个问题的方法有很多种,但都存在缺陷。 然而,JVM 非常擅长消除这种类型的事情,所以我不会太担心它。

编辑:当然,在第二个示例中,您使用了

def
,而
anonfun
是将
def
包装在函数对象中的类。 这当然是需要的,因为
foreach
需要
Function1
。 你必须以某种方式生成那个
Function1

© www.soinside.com 2019 - 2024. All rights reserved.