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 au plus une fois si elle est au niveau supérieur. Déterminer où se trouvent les expressions lambda peut être un peu délicat lorsque vous utilisez du sucre syntaxique comme dans votre exemple, alors convertissons-les en syntaxe désuète équivalente:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Remarque: le rapport Haskell 98 décrit en fait une section d'opérateur de gauche (a %)
comme équivalente à \b -> (%) a b
, mais GHC la désuète (%) a
. Celles-ci sont techniquement différentes car elles peuvent être distinguées par seq
. Je pense que j'ai peut-être soumis un ticket GHC Trac à ce sujet.)
Compte tenu de cela, vous pouvez voir que dans m1'
, l'expression filter odd [1..]
n'est contenue dans aucune 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 la description ci-dessus ne l'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
cela ne dépend pas de x
et réécrire la fonction pour
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
Dans ce cas, la nouvelle version est beaucoup moins efficace car elle devra lire environ 1 Go de la mémoire où y
est stockée, tandis que la version d'origine fonctionnerait dans un espace constant et rentrerait 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'avec elle -O2
.
seq
m1 10000000). Il y a cependant une différence lorsqu'aucun indicateur 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, d'ailleurs (avec moins d'allocation totale lorsque -O2 est utilisé).