Come altri hanno già sottolineato, Haskell richiede una gestione della memoria automatica e dinamica : la gestione automatica della memoria è necessaria perché la gestione manuale della memoria non è sicura; la gestione dinamica della memoria è necessaria perché per alcuni programmi la durata di un oggetto può essere determinata solo in fase di runtime.
Ad esempio, considera il seguente programma:
main = loop (Just [1..1000]) where
loop :: Maybe [Int] -> IO ()
loop obj = do
print obj
resp <- getLine
if resp == "clear"
then loop Nothing
else loop obj
In questo programma, l'elenco [1..1000]
deve essere mantenuto in memoria fino a quando l'utente non digita "cancella"; quindi la durata di questo deve essere determinata dinamicamente, ed è per questo che è necessaria la gestione dinamica della memoria.
Quindi, in questo senso, è necessaria l'allocazione dinamica della memoria automatizzata, e in pratica questo significa: sì , Haskell richiede un garbage collector, dal momento che la garbage collection è il gestore di memoria dinamica automatica più performante.
Però...
Sebbene sia necessario un garbage collector, potremmo provare a trovare alcuni casi speciali in cui il compilatore può utilizzare uno schema di gestione della memoria più economico rispetto alla garbage collection. Ad esempio, dato
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
potremmo sperare che il compilatore rilevi che x2
può essere tranquillamente deallocato quando f
ritorna (piuttosto che aspettare che il garbage collector effettui la deallocazione x2
). In sostanza, chiediamo al compilatore di eseguire l' analisi di escape per convertire le allocazioni in un heap raccolto in modo indesiderato in allocazioni nello stack, ove possibile.
Questo non è troppo irragionevole da chiedere: il compilatore haskell jhc lo fa, sebbene GHC non lo faccia. Simon Marlow afferma che il garbage collector generazionale di GHC rende per lo più inutile l'analisi della fuga.
jhc utilizza in realtà una forma sofisticata di analisi di fuga nota come inferenza della regione . Tener conto di
f :: Integer -> (Integer, Integer)
f x = let x2 = x * x in (x2, x2+1)
g :: Integer -> Integer
g x = case f x of (y, z) -> y + z
In questo caso, un'analisi di escape semplicistica concluderebbe che x2
fuoriesce da f
(perché viene restituito nella tupla) e quindi x2
deve essere allocato nell'heap di Garbage Collection. L'inferenza della regione, d'altra parte, è in grado di rilevare che x2
può essere deallocata al g
ritorno; l'idea qui è che x2
dovrebbe essere assegnato nella g
regione di piuttosto che f
nella regione di.
Oltre Haskell
Sebbene l'inferenza regionale sia utile in alcuni casi come discusso sopra, sembra essere difficile conciliare efficacemente con la valutazione pigra (vedere i commenti di Edward Kmett e Simon Peyton Jones ). Ad esempio, considera
f :: Integer -> Integer
f n = product [1..n]
Si potrebbe essere tentati di allocare l'elenco [1..n]
nello stack e di rilasciarlo dopo i f
ritorni, ma ciò sarebbe catastrofico: cambierebbe f
dall'utilizzo della memoria O (1) (sotto garbage collection) alla memoria O (n).
Negli anni '90 e all'inizio degli anni 2000 è stato svolto un ampio lavoro sull'inferenza regionale per il linguaggio funzionale rigoroso ML. Mads Tofte, Lars Birkedal, Martin Elsman, Niels Hallenberg hanno scritto una retrospettiva abbastanza leggibile sul loro lavoro sull'inferenza regionale, gran parte della quale hanno integrato nel compilatore MLKit . Hanno sperimentato una gestione della memoria puramente basata sulla regione (cioè nessun Garbage Collector) e una gestione ibrida della memoria basata sulla regione / raccolta dei rifiuti, e hanno riferito che i loro programmi di test funzionavano "tra 10 volte più velocemente e 4 volte più lentamente" della pura spazzatura- versioni raccolte.