tl;dr:您的方法loop 是正确的,不会引用流的头部。您可以在无限 Stream 上对其进行测试。
让我们将您的代码示例简化到极限:
class Test {
private[this] var next: Test = _
final def fold(): Int = {
next = new Test
next.fold()
}
}
请注意,您的 loop 方法也是某个对象的方法。
方法是final(就像Stream#foldLeft)——非常重要。
在尾递归优化后使用scalac -Xprint:all test.scala,您将得到:
final def fold(): Int = {
<synthetic> val _$this: Test = Test.this;
_fold(_$this: Test){
({
Test.this.next = new Test();
_fold(Test.this.next)
}: Int)
}
};
而且这段代码不会帮助你理解发生了什么。
通往理解的神奇之地的唯一途径是java字节码。
但是你应该记住一件事:没有method of object 这样的东西。所有方法都是“静态的”。而this 只是方法的第一个参数。如果方法是virtual,会有vtable这样的东西,但是我们的方法是final的,所以这种情况下不会有动态dispatch。
还要注意没有参数这样的东西:所有参数都只是变量,在方法执行之前初始化。
所以this 只是方法的第一个变量(索引 0)。
我们来看看字节码(javap -c Test.class):
public final int fold();
Code:
0: aload_0
1: new #2 // class Test
4: dup
5: invokespecial #16 // Method "<init>":()V
8: putfield #18 // Field next:LTest;
11: aload_0
12: getfield #18 // Field next:LTest;
15: astore_0
16: goto 0
让我们用类似scala的伪代码编写这个方法:
static foo(var this: Test): Int {
:start // label for goto jump
// place variable `this` onto the stack:
// 0: aload_0
// create new `Test`
// 1: new #2 // class Test
// invoke `Test` constructor
// 4: dup
// 5: invokespecial #16 // Method "<init>":()V
// assign `this.next` field value
// 8: putfield #18 // Field next:LTest;
this.next = new Test
// place `this.next` onto the stack
// 11: aload_0
// 12: getfield #18 // Field next:LTest;
// assign `this.next` to variable `this`!
// 15: astore_0
this = this.next // we have no reference to the previous `this`!
// 16: goto 0
goto :start
}
在this = this.next 之后,我们没有在堆栈或第一个变量中引用之前的this。而之前的this可以被GC去掉!
所以Stream#foldLeft 中的tail.foldLeft(...) 将替换为this = this.tail, ...; goto :start。由于this 只是@tailrec 方法的第一个参数,foldLeft 声明才有意义。
现在我们终于可以理解scalac -Xprint:all test.scala的结果了:
final def method(a: A, b: B, ...): Res = {
<synthetic> val _$this: ThisType = ThisType.this;
_method(_$this: Test, a: A, b: B, ...){
({
// body
_method(nextThis, nextA, nextB, ...)
}: Res)
}
};
意思是:
final def method(var this: ThisType, var a: A, var b: B, ...): Res = {
// _method(_$this: Test, a: A, b: B, ...){
:start
// body
// _method(nextThis, nextA, nextB, ...)
this = nextThis
a = nextA
b = nextB
...
goto :start
};
这正是您在 scalac -Xprint:all 方法上使用 loop 之后将得到的结果,但 body 将是巨大的。所以在你的情况下:
...
case Some(x) =>
this = this
s = s.tail
acc = accumulate(x, acc)
goto :start
...
在s = s.tail 之后,您没有引用流的头部。