How to use a simple for loop with imutable var: unexpected result

I have some code in Scastie that shows several variants of the following loop:

    var id = 0
    for 
      i <- 1 until 3
      j <- 1 until 4
      _ = id = id + 1
      e = s"Exp$id"
    do 
      println(s"$id $i $j $e")

The result is:

3 1 1 Exp1
3 1 2 Exp2
3 1 3 Exp3
6 2 1 Exp4
6 2 2 Exp5
6 2 3 Exp6

but I expected:

0 1 1 Exp0
1 1 2 Exp1
2 1 3 Exp2
3 2 1 Exp3
4 2 2 Exp4
5 2 3 Exp5

In other words the variable id should show the same value wherever it appears.

Even this snippet:

    id = 0
    println()
    for 
      i <- 1 until 3
      j <- 1 until 4
      e = s"Exp$id"
    do 
      println(s"$id $i $j $e")
      id = id + 1

surprised me with this result:

0 1 1 Exp0
1 1 2 Exp0
2 1 3 Exp0
3 2 1 Exp3
4 2 2 Exp3
5 2 3 Exp3

Can anyone be so kind as to explain why the id does not have the same value in the println and e value?

Note this is executed in Scala 3 (RC3)

TIA

Your code gets desugared to this (after some cleaning up…):

(1 until 3).foreach(i => (1 until 4).map{ j =>
  id = id + 1
  val e = s"Exp$id"
  (j, e)
}.foreach{
  case (j, e) => println(s"$id $i $j $e")
})

The issue is that that map here is eager so id = id + 1; val e = s"Exp$id"; (j, e) already runs for the entire range 1 until 4 before println(s"$id $i $j $e") is reached.

If you change to lazy evaluation, e.g. by changing to (1 until 4).iterator, evaluation order will be different.

A good rule in for ... do ... statements is probably to keep the for part limited to things that influence or are required for iteration, and put all other variables and side effects in the do part.

3 Likes

You really want to start changing the way you think about iteration; i.e. loops. Your current solution’s shape feels very imperative; i.e. Java-like with mutation. While you can certainly use that kind of approach in Scala, it is definitely considered a fairly extreme code-smell. Extreme in that you will likely fail a code review.

One of the mantras in idiomatic Scala is that if you are having to roll your own iterator, you likely have a higher-level design issue that needs to be addressed. And that once addressed, a pre-existing ALREADY DEEPLY TESTED FOR ONE-OFF ERRORS library iterator should be used.

As the old joke goes:

There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

So, here’s a stab at moving it much closer to idiomatic Scala (Scastie Link):

val asTuples =
  (
    for {
      i <- 1 until 3
      j <- 1 until 4
    } yield (i, j)
  ).zipWithIndex
//println(asTuples)
asTuples
  .foreach {
    case ((i, j), id) =>
      val e = s"Exp$id"
      println(s"$id $i $j $e")
  }

Apologies I reverted to Scala 2 syntax (which remains valid in Scala 3). I just haven’t had the time to learn all of the ins/outs of the new syntax (which from what I am seeing is actually very pleasant to use once learned).

1 Like

I had assumed that all the do/yield stuff would be plugged into the map. Filtering I assumed would be placed in the map that it affected. I keep staring at that last foreach wondering why. Oh well, must be a very good reason.

Hmmmm… I though 2.13.x made all maps lazy. Need I only do that change in the last range?

Will do. No more funny business :sunglasses:

Thank you

Correct. A kludge before refactoring code. Testing some stuff quickly - only it was not so quick.

In the final solution I should be using iterators because I will be generating very long, possibly infinite sets of values (in the order of 20-30). Your solution seems ok but I will have to check if it is lazy.

Thank you.

EDIT:

Indeed. You will get the hang of it quickly.

1 Like

I believe the assignment to the val of asTuples is by default lazy as nothing forces evaluation.

The commented-out println is eager and would force the evaluation of asTuples.

And I am pretty sure the foreach following the val assignment is eager as foreach itself is eager by definition.

1 Like