Como ya han señalado otros, Haskell requiere una gestión automática y dinámica de la memoria: la gestión automática de la memoria es necesaria porque la gestión manual de la memoria no es segura; La administración de memoria dinámica es necesaria porque para algunos programas, la vida útil de un objeto solo se puede determinar en tiempo de ejecución.
Por ejemplo, considere el siguiente programa:
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
En este programa, la lista [1..1000]
debe mantenerse en la memoria hasta que el usuario escriba "borrar"; por lo que la vida útil de esto debe determinarse dinámicamente, y es por eso que la administración dinámica de la memoria es necesaria.
Entonces, en este sentido, la asignación de memoria dinámica automatizada es necesaria, y en la práctica esto significa: sí , Haskell requiere un recolector de basura, ya que la recolección de basura es el administrador automático de memoria dinámica de mayor rendimiento.
Sin embargo...
Aunque es necesario un recolector de basura, podríamos intentar encontrar algunos casos especiales en los que el compilador pueda usar un esquema de administración de memoria más económico que la recolección de basura. Por ejemplo, dado
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
podríamos esperar que el compilador detecte que x2
se puede desasignar de forma segura cuando f
regrese (en lugar de esperar a que el recolector de basura desasigne x2
). Básicamente, le pedimos al compilador que realice un análisis de escape para convertir las asignaciones en un montón de basura recolectada en asignaciones en la pila siempre que sea posible.
No es descabellado pedirlo: el compilador jhc haskell hace esto, aunque GHC no. Simon Marlow dice que el recolector de basura generacional de GHC hace que el análisis de escape sea casi innecesario.
jhc en realidad utiliza una forma sofisticada de análisis de escape conocida como inferencia de región . Considerar
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
En este caso, un análisis de escape simplista concluiría que x2
escapa de f
(porque se devuelve en la tupla) y, por x2
lo tanto, debe asignarse en el montón de recolección de basura. La inferencia de región, por otro lado, es capaz de detectar que x2
se puede desasignar cuando g
regresa; la idea aquí es que x2
debería asignarse en g
la región ' en lugar de f
la región'.
Más allá de Haskell
Si bien la inferencia de región es útil en ciertos casos como se discutió anteriormente, parece ser difícil conciliar efectivamente con la evaluación perezosa (ver los comentarios de Edward Kmett y Simon Peyton Jones ). Por ejemplo, considere
f :: Integer -> Integer
f n = product [1..n]
Uno podría tener la tentación de asignar la lista [1..n]
en la pila y desasignarla después de los f
retornos, pero esto sería catastrófico: cambiaría f
de usar memoria O (1) (bajo recolección de basura) a memoria O (n).
Se realizó un trabajo extenso en la década de 1990 y principios de la de 2000 en la inferencia de regiones para el lenguaje funcional estricto ML. Mads Tofte, Lars Birkedal, Martin Elsman, Niels Hallenberg han escrito una retrospectiva bastante legible sobre su trabajo sobre inferencia de regiones, gran parte del cual integraron en el compilador MLKit . Experimentaron con la administración de memoria puramente basada en regiones (es decir, sin recolector de basura), así como con la administración de memoria híbrida basada en regiones / recolectada de basura, e informaron que sus programas de prueba se ejecutaban "entre 10 veces más rápido y 4 veces más lento" que la basura pura. versiones recopiladas.