Scala如何区分()=> T和=> Ť

时间:2016-10-25 23:46:11

标签: java scala

我的other question作为副本被关闭了,所以我再试一次。我还读过this question,我问的是不同的。我有兴趣了解Call-by-Name: => Type() => Type的不同之处的内部实施。

我的困惑来自于查看javap和cfr反汇编,这显示两种情况没有区别。

e.g。的 ParamTest.scala

object ParamTest {
  def bar(x: Int, y: => Int) : Int = if (x > 0) y else 10
  def baz(x: Int, f: () => Int) : Int = if (x > 0) f() else 20
}

javap输出 javap ParamTest.scala

public final class ParamTest {
  public static int baz(int, scala.Function0<java.lang.Object>);
  public static int bar(int, scala.Function0<java.lang.Object>);
}

CFR反编译输出 java -jar cfr_0_118.jar ParamTest$.class

import scala.Function0;

public final class ParamTest$ {
    public static final ParamTest$ MODULE$;

    public static {
        new ParamTest$();
    }

    public int bar(int x, Function0<Object> y) {
        return x > 0 ? y.apply$mcI$sp() : 10;
    }

    public int baz(int x, Function0<Object> f) {
        return x > 0 ? f.apply$mcI$sp() : 20;
    }

    private ParamTest$() {
        MODULE$ = this;
    }
}

编辑1 Scala语法树scalac -Xprint:parse ParamTest.scala

package <empty> {
  object ParamTest extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    def bar(x: Int, y: _root_.scala.<byname>[Int]): Int = if (x.$greater(0))
      y
    else
      10;
    def baz(x: Int, f: _root_.scala.Function0[Int]): Int = if (x.$greater(0))
      f()
    else
      20
  }
}

编辑2 邮件列表研究

在邮件列表中阅读此interesting post,其中基本上表明=> T已实施为() => T。引用:

  

首先,看看

     

f: => Boolean

     

虽然这被称为&#34; by-name参数&#34;,但实际上它实现为Function0

     

f: () => Boolean

     

只是在两端使用不同的语法。

现在我对this answer更加困惑,明确指出两者是不同的。

问题

  • Scala如何区分barbaz?两者的方法签名(不实现)在反编译代码中是相同的。
  • 这两种情况的区别是不是持久存储在已编译的字节码中?
  • 反编译代码是否不准确?
  • 在编辑1 之后添加:我发现scalac语法树确实显示了差异,bar具有类型_root_.scala.<byname>[Int]的第二个参数。它有什么作用?任何解释,scala source中的指针或等效的伪代码都会有所帮助。
  • 参见上面的编辑2 :引用的块是否正确?如同,=> TFunction0的特殊子类

3 个答案:

答案 0 :(得分:4)

  

Scala如何区分angular 1.5.8bar?方法签名(不是   实现)两者在反编译代码中是相同的。

Scala不需要区分。从它的角度来看,这是两种不同的方法。有趣的是(至少对我来说)是,如果我们将baz重命名为baz并尝试使用“按名称调用”参数创建重载,我们会得到:

bar

对于我们来说,这是一个暗示,在Error:(12, 7) double definition: method bar:(x: Int, f: () => Int)Int and method bar:(x: Int, y: => Int)Int at line 10 have same type after erasure: (x: Int, f: Function0)Int def bar(x: Int, f: () => Int): Int = if (x > 0) f() else 20 的翻译过程中会发生一些事情。

  

这两种情况的区别是不是持久存在于   编译后的字节码?

在Scala发出JVM字节码之前,它还有其他编译阶段。在这种情况下,有趣的是查看“uncurry”阶段(Function0):

-Xprint:uncurry

即使在我们发出字节代码之前,[[syntax trees at end of uncurry]] package testing { object ParamTest extends Object { def <init>(): testing.ParamTest.type = { ParamTest.super.<init>(); () }; def bar(x: Int, y: () => Int): Int = if (x.>(0)) y.apply() else 10; def baz(x: Int, f: () => Int): Int = if (x.>(0)) f.apply() else 20 } } 也会转换为bar

  

反编译代码是否不准确

不,这绝对是准确的。

  

我发现scalac语法树确实显示了差异,bar有   类型 root .scala。[Int]的第二个参数。它是什么   办?

Scala编译分阶段完成,其中每个阶段输出是下一个阶段的输入。除了解析的AST之外,Scala阶段还会创建符号,这样如果一个阶段依赖于特定的实现细节,它将使其可用。 Function0是一个编译符号,表明此方法使用“按名称调用”,因此其中一个阶段可以看到并执行某些操作。

答案 1 :(得分:3)

Scala代码由编译器分析并转换为jvm字节码。在scala级别,你有implicits,非常强大的类型系统,通过名称参数调用和其他类似的东西。在字节码中,一切都消失了。没有咖喱参数,没有含义,只是简单的方法。运行时不需要区分() => A=> A,它只执行字节码。所有检查和验证,您获得的错误都来自分析scala代码的编译器,而不是字节码。在按名称编译的过程中,只用Function0替换,并且这些参数的所有用法都调用了apply方法,但这不会在解析阶段发生,但稍后,这就是为什么你在编译器输出中看到<byname>。试着看一下以后的阶段。

答案 2 :(得分:3)

  

因为Scala以这种方式工作。它将scala代码编译为.class文件并在JVM中执行。因此.class文件应该有必要和充分的信息。

确实如此。此信息存储在名为@ScalaSignature的注释中。 javap -v应该显示它的存在,但它不是人类可读的。

这是必要的,因为Scala签名中有很多信息不能在JVM字节码中表示:不只是按名称与Function0参数,而是访问限定符,参数名称等。