GHC ne mémorise pas les fonctions.
Cependant, il calcule une expression donnée dans le code au plus une fois par fois que son expression lambda environnante est entrée, ou tout au plus une fois si elle est au niveau supérieur. Déterminer où les expressions lambda-sont peut être un peu délicat lorsque vous utilisez du sucre syntaxique comme dans votre exemple, si nous allons convertir en syntaxe Dessucré équivalente:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Note: Le rapport Haskell 98 décrit en fait un opérateur gauche section comme (a %)
comme équivalent à \b -> (%) a b
, mais GHC desugars à (%) a
. ce sont techniquement différents parce qu'ils peuvent être distingués par seq
. Je pense que je pourrais avoir soumis un billet GHC Trac à ce sujet.)
Donné ce, vous pouvez voir que dans m1'
, l'expression filter odd [1..]
n'est pas contenue n toute expression lambda, donc elle ne sera calculée qu'une fois par exécution de votre programme, tandis que dans m2'
, filter odd [1..]
sera calculée chaque fois que l'expression lambda est entrée, c'est-à-dire, à chaque appel de m2'
. Cela explique la différence de timing que vous voyez.
En fait, certaines versions de GHC, avec certaines options d'optimisation, partageront plus de valeurs que ce que la description ci-dessus indique. Cela peut être problématique dans certaines situations. Par exemple, considérons la fonction
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC peut remarquer que y
ne dépend pas de x
et récrire la fonction à
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
Dans ce cas, la nouvelle version est beaucoup moins efficace parce qu'il aura lire environ 1 Go de la mémoire où y
est stocké, tandis que la version d'origine s'exécuterait dans un espace constant et entrer dans le cache du processeur. En fait, sous GHC 6.12.1, la fonction f
est presque deux fois plus rapide lorsqu'elle est compilée sans optimisations qu'elle n'est compilée avec -O2
.
Le coût à évaluer (filtre impair [1 ..]) expression est de toute façon proche de zéro - c'est une liste paresseuse après tout, donc le coût réel est dans l'application (x !! 10000000) quand la liste est réellement évalué. En outre, m1 et m2 semblent être évalués une seule fois avec -O2 et -O1 (sur mon ghc 6.12.3) au moins dans le test suivant: (test = m1 10000000 'seq' m1 10000000). Il y a une différence quand aucun drapeau d'optimisation n'est spécifié. Et les deux variantes de votre "f" ont une résidence maximale de 5356 octets indépendamment de l'optimisation, soit dit en passant (avec moins d'allocation totale quand -O2 est utilisé). –
@ Ed'ka: Essayez ce programme de test, avec la définition ci-dessus de 'f':' main = interact $ unlines. (montrer, carte f, lire). lignes »; compiler avec ou sans '-O2'; alors 'echo 1 | ./main'. Si vous écrivez un test comme 'main = print (f 5)', alors 'y' peut être récupéré comme il est utilisé et il n'y a pas de différence entre les deux' f'. –
er, cela devrait être 'map (show .f read)', bien sûr. Et maintenant que j'ai téléchargé GHC 6.12.3, je vois les mêmes résultats que dans GHC 6.12.1. Et oui, vous avez raison sur les versions "m1" et "m2" d'origine: les versions de GHC qui effectuent ce type de levage avec des optimisations activées transformeront "m2" en "m1". –