var z = 10
  for (i <- 1 to 3; x = i + 1; y = z - 1) {
    println(x)
    println(y)
    z -= 1
  }

Salida:

2 9 3 9 4 9

No es lo que esperaba. Parece que el ciclo for siempre mantiene z = 10. ¿Puede alguien ayudarme a explicar esto?

0
梁嘉腾 23 feb. 2018 a las 01:46

2 respuestas

La mejor respuesta

Puede inspeccionar lo que está ejecutando de la siguiente manera:

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)
  }
}))

Si ve el código expandido, creo que debería ser suficiente para convencerle de que y siempre debería ser 9.

2
Jason Hu 22 feb. 2018 a las 22:58

Los for de Scala no son bucles imperativos, son comprensiones monádicas generales. Cada for y for-yield se reescribe en una secuencia de aplicaciones de los métodos foreach, map, flatMap y withFilter.

Por ejemplo, esto aquí:

for (x <- generator) yield { f(x) }

Se reescribe en

generator.map(x => f(x))

Si usa definiciones como x = ... dentro de for - comprensiones, se reescriben de la siguiente manera:

for {
  g <- generator
  x = xRhs
} {
  doBlah()
}

Se reescribe a

for {
  (g, x) <- for (g <- generator) yield (g, xRhs)
} {
  doBlah()
}

Y luego a

(for (g <- generator) yield (g,xRhs)).foreach{ 
  case (g, x) => doBlah() 
}

Y finalmente a

generator.map(g => (g, xRhs)).foreach{ case (g, x) => doBlah() }

Para su ejemplo, significa la siguiente secuencia de reescritura horrible (copie y pegue, ejecútelo, intente seguir cada paso de reescritura):

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
  }
}

El resultado final es (moralmente equivalente a) algo como esto:

  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
  }

Como puede ver, el z - 1 en el segundo map se evalúa incluso antes de que comience la iteración foreach. Por lo tanto, y permanece atascado en 9.

¿Cómo evitarlo? Simplemente evite usar var s en combinación con for - comprensiones. Mejor: use var s con moderación en general. O al menos, muévelos dentro del bloque:

  var z = 10
  for (i <- 1 to 3) {
    val x = i + 1
    val y = z - 1
    println(x)
    println(y)
    z -= 1
  }
2
Andrey Tyukin 22 feb. 2018 a las 23:29