加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala Streams:如何避免保持对头部(和其他元素)的引用

发布时间:2020-12-16 09:49:31 所属栏目:安全 来源:网络整理
导读:假设我有一个循环遍历Stream元素的尾递归方法,就像这样(简化代码,未测试): @tailrecdef loop(s: Stream[X],acc: Y): Y = s.headOption match { case None = acc case Some(x) = loop(s.tail,accumulate(x,acc)) } 在迭代时我是否保持对流的头部(以及所有其
假设我有一个循环遍历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)

编辑:
我知道这可以在没有尾递归的情况下轻松完成(例如使用foldLeft),但非简化代码不是一次只循环一个元素(有时使用s代替s.tail,有时使用s.tail.dropWhile(.. .)使用.所以我想找出如何正确使用Stream.

解决方法

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之后,你没有引用流的头部.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读