Pourquoi la fonction «ne rien faire» de Haskell, id, consomme-t-elle des tonnes de mémoire?

112

Haskell a une fonction d'identité qui renvoie l'entrée inchangée. La définition est simple:

id :: a -> a
id x = x

Donc, pour le plaisir, cela devrait afficher 8:

f = id id id id id id id id id id id id id id id id id id id id id id id id id id id
main = print $ f 8

Après quelques secondes (et environ 2 Go de mémoire selon le Gestionnaire des tâches), la compilation échoue avec ghc: out of memory. De même, dit l'interprète ghci: out of memory.

Comme idc'est une fonction assez simple, je ne m'attendrais pas à ce que ce soit une charge de mémoire au moment de l'exécution ou de la compilation. À quoi sert toute la mémoire?

Ryan
la source
11
Vous voulez composer ces ids. En VIM, avec le curseur sur la définition f, faites ceci: :s/id id/id . id ./g.
Tobias Brandt

Réponses:

135

Nous connaissons le type de id,

id :: a -> a

Et quand nous nous spécialisons ceci pour id id, la copie de gauche de ida le type:

id :: (a -> a) -> (a -> a)

Et puis, lorsque vous spécialisez à nouveau cela pour le plus idà gauche id id id, vous obtenez:

id :: ((a -> a) -> (a -> a)) -> ((a -> a) -> (a -> a))

Donc, vous voyez chaque que idvous ajoutez, la signature de type la plus à gauche idest deux fois plus grande.

Notez que les types sont supprimés lors de la compilation, donc cela ne prendra que de la mémoire dans GHC. Cela ne prendra pas de mémoire dans votre programme.

Dietrich Epp
la source
Je me souviens qu'Okasaki a rencontré des problèmes similaires lorsqu'il a écrit une calculatrice RPN intégrée à Haskell.
dfeuer
3
La question est peut-être de savoir si GHC devrait trouver un moyen de gérer ce genre de choses plus gracieusement. En particulier, le type est très grand lorsqu'il est écrit en entier, mais il y a énormément de duplication - le partage pourrait-il être utilisé pour compresser de telles choses? Existe-t-il un moyen efficace de les traiter?
dfeuer
5
@dfeuer Essayez de demander le type dans ghci. Vous verrez par la rapidité de la réponse qu'il doit faire le partage approprié. Je soupçonne que ce partage est perdu - pour des raisons évidentes - une fois que vous traduisez en une autre représentation intermédiaire (par exemple le noyau).
Daniel Wagner
4
Cela signifie que si idest répété nfois, l'espace de son type est proportionnel à 2^n. Le type inféré dans le code de Ryan aurait besoin de 2^27références à sa variable de type en plus de l'autre structure nécessaire pour représenter le type, qui est probablement beaucoup plus grande que ce que vous attendez de la plupart des types.
David
58
Faire de l'inférence de type naïvement est double exponentiel, en utilisant intelligemment le partage des expressions de type, vous pouvez le réduire à juste exponentiel. Mais peu importe ce que vous faites, il y aura des expressions assez simples qui feront exploser le vérificateur de type. Heureusement, cela ne se produit pas dans la programmation pratique.
août