我的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更加困惑,明确指出两者是不同的。
问题:
bar
与baz
?两者的方法签名(不实现)在反编译代码中是相同的。bar
具有类型_root_.scala.<byname>[Int]
的第二个参数。它有什么作用?任何解释,scala source中的指针或等效的伪代码都会有所帮助。=> T
是Function0
的特殊子类答案 0 :(得分:4)
Scala如何区分
angular 1.5.8
与bar
?方法签名(不是 实现)两者在反编译代码中是相同的。
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
参数,而是访问限定符,参数名称等。