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.
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).
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?
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.