Pregunta

En F # WikiBook en la sección Encapsulando el estado mutable , hay un fragmento de código siguiente .

> let incr =
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter;;

val incr : (unit -> int)

> incr();;
val it : int = 1

> incr();;
val it : int = 2

> incr();;
val it : int = 3

Al principio, parecía bastante fácil tragarse el hecho de que el valor mutable del counter aumenta cada vez que se invoca incr .

Pero después de pensarlo por un tiempo, lo que no pude entender fue cuándo counter se libera del montón y también cómo counter todavía se refiere al valor anterior antes de ser incrementado . ¿Cómo es que el contador que vive dentro del alcance de la función incr sobrevive a través de múltiples llamadas de función?

Entonces las preguntas principales son:

  • ¿Cuándo se libera counter del montón?
  • ¿No es counter una pérdida de memoria?
¿Fue útil?

Solución

La distinción entre 'alcance léxico' (donde un nombre tiene significado en el texto de un programa) y 'duración' (duración del tiempo de ejecución entre el momento en que se crea y destruye el objeto) a veces puede ser confusa, ya que a menudo estos dos están altamente correlacionados . Sin embargo, la técnica demostrada en este ejemplo es común en lenguajes funcionales: le da a un detalle de implementación un pequeño alcance léxico (que oculta los detalles de implementación de las personas que llaman), pero extiende su tiempo de vida al capturarlo en un cierre (para que su tiempo de vida se convierta en el tiempo de vida del objeto envolvente, en este caso la función 'incr'). Esta es una forma común de encapsular en programación funcional (en contraste con la técnica de encapsulación habitual de público / privado en clases en programación orientada a objetos).

Ahora, en este ejemplo en particular, parece que 'incr' es una función de nivel superior, lo que significa que su valor dura toda la vida del programa (o sesión interactiva si se escribe en fsi.exe). Podría llamar a esto una "fuga", pero depende de la intención. Si tiene un contador de identificación único que necesita durante toda la vida de todo su programa, entonces tendrá que almacenar ese contador valioso en algún lugar que dura todo el programa. Entonces, esto es 'una fuga' o 'una característica de diseño' dependiendo de cómo se usará 'incr' (¿necesitará usar esa función para todo el resto del programa?). En cualquier caso, el punto clave aquí es que 'incr' contiene recursos de memoria, por lo que si no necesita esos recursos para siempre, debe hacer arreglos para que el cierre al que hace referencia 'incr' se vuelva inalcanzable cuando ya no sea necesario. Comúnmente, esto podría ser haciendo que sea local para alguna otra función, por ejemplo

let MyComplicatedFuncThatNeedsALocalCounter args =
    let incr = 
        // as before
    // other code that uses incr
    // return some result that does not capture incr

Otros consejos

En este caso, incr es una función de nivel superior (implementada como un campo estático si no me equivoco). Tiene un cierre que a su vez tiene una referencia a esa celda de referencia llamada counter . Mientras exista este cierre, la celda ref se mantiene en la memoria.

Ahora, este enlace de nivel superior nunca obtendrá basura recolectada ya que es un campo de solo lectura estático. (en términos de C #). Sin embargo, si tiene cierres como ese con una vida útil limitada (enlazada localmente o en un objeto), la celda ref se liberará cuando el cierre se recolecte basura.

El contador

se libera del montón cuando ya no se puede acceder a incr. No es una pérdida de memoria debido a la recolección de basura.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top