GHC ne mémorise pas les fonctions.
Cependant, il calcule une expression donnée dans le code au maximum une fois à chaque fois que l'expression lambda qui l'entoure est saisie, ou au maximum une fois à chaque fois si elle se trouve au niveau supérieur. Déterminer où se trouvent les expressions lambda peut être un peu délicat lorsque vous utilisez un sucre syntaxique comme dans votre exemple, alors convertissons-les en syntaxe désucrée équivalente :
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Note : Le rapport Haskell 98 décrit en fait une section d'opérateur gauche comme (a %)
comme équivalent à \b -> (%) a b
mais GHC le désucre en (%) a
. Ils sont techniquement différents car ils peuvent être distingués par seq
. Je pense que j'ai peut-être soumis un ticket Trac GHC à ce sujet).
Compte tenu de cela, vous pouvez voir que dans m1'
l'expression filter odd [1..]
n'est pas contenue dans une expression lambda, elle ne sera donc calculée qu'une seule fois par exécution de votre programme, alors que dans l'expression m2'
, filter odd [1..]
sera calculée à chaque fois que l'expression lambda est saisie, c'est-à-dire à chaque appel de la fonction 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 pourrait remarquer que y
ne dépend pas de x
et réécrire la fonction en
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 mémoire où y
est stockée, alors que la version originale s'exécuterait en espace constant et tiendrait dans le cache du processeur. En fait, sous GHC 6.12.1, la fonction f
est presque deux fois plus rapide lorsqu'il est compilé sans qu'il est compilé avec -O2
.