Reputation: 11
var z = 10
for (i <- 1 to 3; x = i + 1; y = z - 1) {
println(x)
println(y)
z -= 1
}
Output:
2 9 3 9 4 9
It is not what I expect. It seems the for loop always keeps z = 10. Can someone help me explain this?
Upvotes: 0
Views: 254
Reputation: 44908
Scala's for
are not imperative loops, they are general monadic comprehensions. Every for
and for-yield
is rewritten into a sequence of applications of methods foreach
, map
, flatMap
and withFilter
.
For example, this here:
for (x <- generator) yield { f(x) }
is rewritten into
generator.map(x => f(x))
If you use definitions like x = ...
inside of for
-comprehensions, they are rewritten as follows:
for {
g <- generator
x = xRhs
} {
doBlah()
}
is rewritten to
for {
(g, x) <- for (g <- generator) yield (g, xRhs)
} {
doBlah()
}
and then to
(for (g <- generator) yield (g,xRhs)).foreach{
case (g, x) => doBlah()
}
and finally to
generator.map(g => (g, xRhs)).foreach{ case (g, x) => doBlah() }
For your example it means the following horrific rewriting sequence (copy-paste it, run it, try to follow each rewriting step):
def ----- : Unit = println("-" * 40)
{
var z = 10
for (i <- 1 to 3; x = i + 1; y = z - 1) {
// println(s"i = $i , x = $x , y = $y, z = $z")
println(x)
println(y)
z -= 1
}
}
-----
{
var z = 10
for {
(i, x) <- for (i <- 1 to 3) yield (i, i + 1)
y = z - 1
} {
println(x)
println(y)
z -= 1
}
}
-----
{
var z = 10
for {
((i, x), y) <- for ((i, x) <- for (i <- 1 to 3) yield (i, i + 1)) yield ((i, x), z - 1)
} {
println(x)
println(y)
z -= 1
}
}
-----
{
var z = 10
for {
((i, x), y) <- for ((i, x) <- (1 to 3).map(i => (i, i + 1))) yield ((i, x), z - 1)
} {
println(x)
println(y)
z -= 1
}
}
-----
{
var z = 10
for {
((i, x), y) <- (1 to 3).map(i => (i, i + 1)).map(ix => (ix, z - 1))
} {
println(x)
println(y)
z -= 1
}
}
-----
{
var z = 10
(1 to 3).map(i => (i, i + 1)).map(ix => (ix, z - 1)).foreach {
case ((i, x), y) =>
println(x)
println(y)
z -= 1
}
}
The final result is (morally equivalent to) something like this:
var z = 10
(1 to 3).map(i => (i, i + 1)).map(ix => (ix, z - 1)).foreach {
case ((i, x), y) =>
println(x)
println(y)
z -= 1
}
As you can see, the z - 1
in the second map
is evaluated even before the foreach
-iteration starts. Therefore y
remains stuck at 9
.
How to avoid it? Just avoid using var
s in combination with for
-comprehensions. Better: use var
s sparingly in general. Or at least, move them inside the block:
var z = 10
for (i <- 1 to 3) {
val x = i + 1
val y = z - 1
println(x)
println(y)
z -= 1
}
Upvotes: 2
Reputation: 6333
You can inspect what you are running as following:
scala> import scala.reflect.runtime.{universe => ru}
scala> import ru._
scala> q"""for (i <- 1 to 3; x = i + 1; y = z - 1) {
| println(x)
| println(y)
| z -= 1
| }
| """
res13: reflect.runtime.universe.Tree =
1.to(3).map(((i) => {
val x = i.$plus(1);
val y = z.$minus(1);
scala.Tuple3(i, x, y)
})).foreach(((x$1) => x$1: @scala.unchecked match {
case scala.Tuple3((i @ _), (x @ _), (y @ _)) => {
println(x);
println(y);
z.$minus$eq(1)
}
}))
If you see the expanded code, I believe it should be enough to convince you that y
should always be 9.
Upvotes: 2