Scala Streams: Avoiding Attachment to the Head (and Other Elements)

Suppose I have a tail recursive method that iterates over a elements Streamlike this (simplified code, not tested):

@tailrec
def loop(s: Stream[X], acc: Y): Y = 
  s.headOption match {
    case None => acc
    case Some(x) => loop(s.tail, accumulate(x, acc))
  }

Do I keep referring to the head (and all other elements) of the stream during the iteration, which I know should be avoided? If so, what is the best way to achieve the same?

The code that calls this (I hope) does not support the link. Suppose that listis List[X], then the code calls

loop(list.sliding(n).toStream, initialY)

EDIT: I know this can be easily done without tail recursion (like using foldLeft), but the non-simplified code doesn't loop exactly one element at a time (sometimes it is s.tailused instead s, and sometimes s.tail.dropWhile(...)that's why I'm looking to find out how to use it correctly Stream.

+4
source share
2 answers

tl; dr: your method is loopcorrect, a link to the stream header will not be specified. You can test it on the infinite Stream.

Simplify your code example to the limit:

class Test {
  private[this] var next: Test = _

  final def fold(): Int = {
    next = new Test
    next.fold()
  }
}

Note that your method is loopalso a method of some object.

The method final(just like Stream#foldLeft) is very important.

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 - . , , vtable, , .

, , : , .

, 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!

, tail.foldLeft(...) Stream#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 .

+6

, , , " ?"

, , loop, val.

, , :

list.sliding(n).foldLeft(initialY)(accumulate)
+1

All Articles