Scala Streams:如何避免保持对头部(和其他元素)的引用
假设我有一个循环遍历Stream元素的尾递归方法,就像这样(简化代码,未测试):
@tailrec def loop(s: Stream[X],acc: Y): Y = s.headOption match { case None => acc case Some(x) => loop(s.tail,accumulate(x,acc)) } 在迭代时我是否保持对流的头部(以及所有其他元素)的引用,我知道应该避免这种情况吗? 调用它的代码是(我希望)不保留引用.假设list是List [X],那么代码正在调用 loop(list.sliding(n).toStream,initialY) 编辑: 解决方法
tl; dr:你的方法循环是正确的,没有引用流的头部.你可以在无限的Stream上测试它.
让我们将代码示例简化为极限: class Test { private[this] var next: Test = _ final def fold(): Int = { next = new Test next.fold() } } 请注意,您的循环方法也是某个对象的方法. 该方法是最终的(就像Stream#foldLeft一样) – 这非常重要. 使用scalac -Xprint:尾递归优化之后的所有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字节码. 但你应该记住一件事:没有对象的方法.所有方法都是“静态的”.这只是该方法的第一个参数.如果方法是虚拟的,那就是vtable,但我们的方法是最终的,所以在这种情况下不会有动态调度. 还要注意,没有参数这样的东西:所有参数都只是变量,在方法执行之前初始化. 所以这只是方法的第一个变量(索引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之后,我们没有在堆栈或第一个变量中引用前一个.以前这可以通过GC删除! 所以Stream#foldLeft中的tail.foldLeft(…)将替换为this = this.tail,…;转到:开始.因为在foldLeft声明之前,这只是方法@tailrec的第一个参数. 现在我们终于可以理解scalac -Xprint:所有test.scala结果: final def method(a: A,b: B,...): Res = { <synthetic> val _$this: ThisType = ThisType.this; _method(_$this: Test,a: A,...){ ({ // body _method(nextThis,nextA,nextB,...) }: Res) } }; 手段: final def method(var this: ThisType,var a: A,var b: B,...): Res = { // _method(_$this: Test,...){ :start // body // _method(nextThis,...) this = nextThis a = nextA b = nextB ... goto :start }; 这正是你在scalac -Xprint之后得到的:所有你的循环方法,但身体将是巨大的.所以在你的情况下: ... case Some(x) => this = this s = s.tail acc = accumulate(x,acc) goto :start ... 在s = s.tail之后,你没有引用流的头部. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |