Comme d'autres l'ont déjà souligné, Haskell nécessite une gestion automatique et dynamique de la mémoire: la gestion automatique de la mémoire est nécessaire car la gestion manuelle de la mémoire n'est pas sûre; la gestion dynamique de la mémoire est nécessaire car pour certains programmes, la durée de vie d'un objet ne peut être déterminée qu'au moment de l'exécution.
Par exemple, considérez le programme suivant:
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
Dans ce programme, la liste [1..1000]
doit être conservée en mémoire jusqu'à ce que l'utilisateur tape "clear"; donc la durée de vie de celui-ci doit être déterminée dynamiquement, et c'est pourquoi une gestion dynamique de la mémoire est nécessaire.
Donc, dans ce sens, l'allocation de mémoire dynamique automatisée est nécessaire, et en pratique cela signifie: oui , Haskell nécessite un garbage collector, puisque garbage collection est le gestionnaire de mémoire dynamique automatique le plus performant.
Toutefois...
Bien qu'un garbage collector soit nécessaire, nous pourrions essayer de trouver des cas particuliers où le compilateur peut utiliser un schéma de gestion de mémoire moins cher que le garbage collection. Par exemple, étant donné
f :: Integer -> Integer
f x = let x2 = x*x in x2*x2
nous pourrions espérer que le compilateur détecte ce qui x2
peut être libéré en toute sécurité lors du f
retour (plutôt que d'attendre que le ramasse-miettes se désalloue x2
). Essentiellement, nous demandons au compilateur d'effectuer une analyse d'échappement pour convertir les allocations en tas récupéré en mémoire en allocations sur la pile dans la mesure du possible.
Ce n'est pas trop déraisonnable de demander: le compilateur jhc haskell fait cela, bien que GHC ne le fasse pas. Simon Marlow dit que le ramasse-miettes générationnel de GHC rend l'analyse des évasions pratiquement inutile.
jhc utilise en fait une forme sophistiquée d'analyse d'échappement connue sous le nom d' inférence de région . Considérer
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
Dans ce cas, une analyse d'échappement simpliste conclurait que x2
s'échappe de f
(car il est retourné dans le tuple), et x2
doit donc être alloué sur le tas récupéré. L'inférence de région, d'autre part, est capable de détecter ce qui x2
peut être désalloué lors du g
retour; l'idée ici est que cela x2
devrait être attribué dans g
la région de s plutôt que dans f
la région de s.
Au-delà de Haskell
Bien que l'inférence de région soit utile dans certains cas comme discuté ci-dessus, elle semble difficile à concilier efficacement avec une évaluation paresseuse (voir les commentaires d'Edward Kmett et Simon Peyton Jones ). Par exemple, considérez
f :: Integer -> Integer
f n = product [1..n]
On pourrait être tenté d'allouer la liste [1..n]
sur la pile et de la désallouer après les f
retours, mais ce serait catastrophique: cela passerait f
de l'utilisation de la mémoire O (1) (sous garbage collection) à la mémoire O (n).
Un travail approfondi a été effectué dans les années 1990 et au début des années 2000 sur l'inférence de région pour le langage fonctionnel strict ML. Mads Tofte, Lars Birkedal, Martin Elsman, Niels Hallenberg ont écrit une rétrospective assez lisible sur leur travail sur l'inférence de région, dont une grande partie a été intégrée au compilateur MLKit . Ils ont expérimenté la gestion de la mémoire purement basée sur la région (c'est-à-dire pas de ramasse-miettes) ainsi que la gestion hybride de la mémoire basée sur la région / ramasse-miettes, et ont signalé que leurs programmes de test fonctionnaient «entre 10 fois plus vite et 4 fois plus lentement» que le pur garbage- versions collectées.