Domanda

Su F # WikiBook nella sezione Encapsulating Mutable State , c'è un frammento di codice seguente .

> 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

Inizialmente, sembrava abbastanza facile ingoiare il fatto che, il valore contatore mutabile aumenta ogni volta che viene invocato incr .

Ma dopo averci pensato per un po ', quello che non riuscivo a capire era quando counter è stato liberato dall'heap e anche come counter si riferisce ancora al valore precedente prima di essere incrementato . In che modo il contatore che vive all'interno dell'ambito della funzione incr sopravvive attraverso più chiamate di funzione?

Quindi le domande principali sono:

  • Quando counter viene liberato dall'heap?
  • counter non è una perdita di memoria?
È stato utile?

Soluzione

La distinzione tra 'ambito lessicale' (dove un nome ha significato nel testo di un programma) e 'vita' (durata del runtime tra quando l'oggetto viene creato e distrutto) può talvolta essere fonte di confusione, poiché spesso questi due sono altamente correlati . Tuttavia, la tecnica dimostrata da questo esempio è comune nei linguaggi funzionali: dai a un dettaglio di implementazione un piccolo ambito lessicale (che nasconde i dettagli di implementazione dai chiamanti), ma estendi la sua durata catturandolo in una chiusura (in modo che la sua vita diventi la vita dell'oggetto che racchiude - in questo caso la funzione 'incr'). Questo è un modo comune per fare l'incapsulamento nella programmazione funzionale (in contrasto con la solita tecnica di incapsulamento di pubblico / privato nelle classi nella programmazione orientata agli oggetti).

Ora, in questo particolare esempio, sembra che "incr" sia una funzione di livello superiore, il che significa che il suo valore dura per la durata del programma (o sessione interattiva se si digita in fsi.exe). Potresti definirlo una "perdita", ma dipende dalle intenzioni. Se si dispone di un contatore ID univoco necessario per l'intera durata dell'intero programma, sarà necessario memorizzare il contatore varabile da qualche parte che dura per l'intero programma. Quindi, questa è 'una perdita' o 'una caratteristica di progettazione' a seconda di come verrà usato 'incr' (dovrai usare quella funzione per tutto il resto del programma?). In ogni caso, il punto chiave qui è che "incr" contiene risorse di memoria, quindi se non hai bisogno di tali risorse per sempre, dovresti fare in modo che la chiusura a cui fa riferimento "incr" diventi irraggiungibile quando non è più necessaria. Comunemente ciò potrebbe essere rendendolo locale ad un'altra funzione, ad esempio

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

Altri suggerimenti

In questo caso, incr è una funzione di livello superiore (implementata come campo statico se non sbaglio). Contiene una chiusura che a sua volta ha un riferimento a quella cella di riferimento denominata contatore . Finché esiste questa chiusura, la cella ref viene mantenuta in memoria.

Ora questa associazione di livello superiore non verrà mai raccolta dei rifiuti in quanto è un campo di sola lettura statico. (in termini di C #). Se, tuttavia, hai chiusure del genere con una durata limitata (associata localmente o in un oggetto), la cella ref verrà liberata quando la chiusura viene raccolta in modo inutile.

Il contatore

viene liberato dall'heap quando incr non è più raggiungibile. Non è una perdita di memoria a causa della garbage collection.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top