tutorial tipos superior reservadas palabras pagina orden oficial listas herramienta funciones ejemplos development datos con scala scope lazy-evaluation function-literal

scala - tipos - ¿Cómo debo evitar capturar involuntariamente el alcance local en los literales de funciones?



scala tutorial (1)

Preguntaré esto con un ejemplo de Scala, pero bien podría ser que esto afecte a otros idiomas que permiten estilos imperativos y funcionales híbridos.

Aquí hay un pequeño ejemplo ( ACTUALIZADO , ver a continuación):

def method: Iterator[Int] { // construct some large intermediate value val huge = (1 to 1000000).toList val small = List.fill(5)(scala.util.Random.nextInt) // accidentally use huge in a literal small.iterator filterNot ( huge contains _ ) }

Ahora iterator.filterNot funciona perezosamente, ¡lo cual es genial! Como resultado, esperaríamos que el iterador devuelto no consuma mucha memoria (de hecho, O (1)). Tristemente, sin embargo, hemos cometido un terrible error: dado que filterNot es flojo, guarda una referencia a la función literal que huge contains _ .

Por lo tanto, si bien pensamos que el método requeriría una gran cantidad de memoria mientras se estaba ejecutando, y que esa memoria podría liberarse inmediatamente después de la terminación del método, de hecho esa memoria está atascada hasta que olvidemos el Iterator devuelto.

(¡Acabo de cometer un error así, que tardó mucho tiempo en rastrear! Puedes ver cosas como estas mirando montones de montones ...)

¿Cuáles son las mejores prácticas para evitar este problema?

Parece que la única solución es verificar cuidadosamente los literales de funciones que sobreviven al final del alcance y que capturaron variables intermedias. Esto es un poco incómodo si está construyendo una colección no estricta y planea regresarla. ¿Alguien puede pensar en algunos trucos agradables, específicos de Scala o de otro tipo, que eviten este problema y me permitan escribir un buen código?

ACTUALIZACIÓN: el ejemplo que había dado anteriormente era estúpido, como lo demuestra la respuesta de huynhjl a continuación. Había sido:

def method: Iterator[Int] { val huge = (1 to 1000000).toList // construct some large intermediate value val n = huge.last // do some calculation based on it (1 to n).iterator map (_ + 1) // return some small value }

De hecho, ahora que entiendo un poco mejor cómo funcionan estas cosas, ¡no estoy tan preocupado!


¿Estás seguro de que no estás simplificando demasiado el caso de prueba? Esto es lo que corro:

object Clos { def method: Iterator[Int] = { val huge = (1 to 2000000).toList val n = huge.last (1 to n).iterator map (_ + 1) } def gc() { println("GC!!"); Runtime.getRuntime.gc } def main(args:Array[String]) { val list = List(method, method, method) list.foreach(m => println(m.next)) gc() list.foreach(m => println(m.next)) list.foreach(m => println(m.next)) } }

Si te entiendo correctamente, porque main está usando los iteradores incluso después de la llamada a gc() , la JVM estaría reteniendo los objetos huge .

Así es como lo manejo:

JAVA_OPTS="-verbose:gc" scala -cp classes Clos

Esto es lo que imprime hacia el final:

[Full GC 57077K->57077K(60916K), 0.3340941 secs] [Full GC 60852K->60851K(65088K), 0.3653304 secs] 2 2 2 GC!! [Full GC 62959K->247K(65088K), 0.0610994 secs] 3 3 3 4 4 4

Así que me parece que los objetos huge fueron recuperados ...