I stumbled upon some really weird behaviour with s...
# announcements
m
I stumbled upon some really weird behaviour with sequences while working on todays AdventOfCode. I’ve managed to strip the problem down to these three lines. This throws a StackOverflowError:
Copy code
fun main() {
    var curr = listOf(1).asSequence()

    curr = curr.map { curr.sum() }

    println(curr.sum())
}
While this works fine:
Copy code
fun main() {
    var curr = listOf(1).asSequence()

    val done = curr.map { curr.sum() }

    println(done.sum())
}
Do anyone know why this happens? It also works if I remove the
sum
inside the
map
. The weird thing is that it makes a difference whether I assign the result to a new variable or not.
🤔 1
m
If I understudy those Sequences right, you're creating a loop. The sequence is evaluated lazily and because of that the new value of curr references itself.
w
Having this:
Copy code
@Test
    fun asas() {
        println("start")
        var curr = listOf(1).asSequence()
        println("created curr")
        curr = curr.map {
            println("going to do the sum inside the map")
            curr.sum().also { println("doing the sum inside map") }
        }
        println("going to do the last sum")
        println(curr.sum())
    }
Will produce
Copy code
start
created curr
going to do the last sum
going to do the sum inside the map
going to do the sum inside the map
going to do the sum inside the map
going to do the sum inside the map (and this until StackOverFlow)
m
Basicly you're telling the sequence to map each value to the sum of itself, but because there are lazily the sum of itself will be calculated by all values which intern will be calculated by the sum, which will be calculated by the whole sequence, and so on.
w
The bytecodes:
Copy code
public final void asas2() {
      final ObjectRef curr = new ObjectRef();
      curr.element = CollectionsKt.asSequence((Iterable)CollectionsKt.listOf(1));
      curr.element = SequencesKt.map((Sequence)curr.element, (Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            return this.invoke(((Number)var1).intValue());
         }

         public final int invoke(int it) {
            return SequencesKt.sumOfInt((Sequence)curr.element);
         }
      }));
      int var2 = SequencesKt.sumOfInt((Sequence)curr.element);
      boolean var3 = false;
      System.out.println(var2);
   }

   public final void asas3() {
      final ObjectRef curr = new ObjectRef();
      curr.element = CollectionsKt.asSequence((Iterable)CollectionsKt.listOf(1));
      Sequence done = SequencesKt.map((Sequence)curr.element, (Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            return this.invoke(((Number)var1).intValue());
         }

         public final int invoke(int it) {
            return SequencesKt.sumOfInt((Sequence)curr.element);
         }
      }));
      int var3 = SequencesKt.sumOfInt(done);
      boolean var4 = false;
      System.out.println(var3);
   }
The
asas2
is the one with problem, the
asas3
is the other one “without” the problem.
m
Ah, now I see. Because when the
map
is actually executed (triggered by the final sum), the reference to
curr
has already changed.
So this works fine, because I capture the original sequence first:
Copy code
fun main() {
    var curr = listOf(1).asSequence()
    val capture = curr

    curr = curr.map { capture.sum() }

    println(curr.toList())
}
w
I’m not sure, but in the
asas2
(with the problem), it is adding the
map
operation as an
element
in the sequence, would that make a difference when the `map`executes, and getting “lost” in how many times it needs to execute?