GHC не запоминает функции.
Однако он вычисляет любое данное выражение в коде не чаще одного раза за каждый раз, когда вводится его окружающее лямбда-выражение, или самое большее один раз, если оно находится на верхнем уровне. Определить, где находятся лямбда-выражения, может быть немного сложно, когда вы используете синтаксический сахар, как в вашем примере, поэтому давайте преобразуем их в эквивалентный синтаксис desugared:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Примечание. В отчете на Haskell 98 фактически описывается левая секция оператора, такая как (a %)
, эквивалентная \b -> (%) a b
, но GHC обнуляет ее до (%) a
. Они технически отличаются, поскольку их можно отличить seq
. Я думаю, что мог бы подать билет GHC Trac об этом.)
Учитывая это, вы можете видеть, что в m1'
выражение filter odd [1..]
не содержится ни в одном лямбда-выражении, поэтому оно будет вычисляться только один раз за запуск вашей программы, тогда как в m2'
, filter odd [1..]
будет вычисляться каждый раз при вводе лямбда-выражения, т. е. при каждом вызове m2'
. Это объясняет разницу во времени, которое вы видите.
На самом деле, некоторые версии GHC с определенными параметрами оптимизации будут иметь больше значений, чем указано в приведенном выше описании. Это может быть проблематично в некоторых ситуациях. Например, рассмотрим функцию
f = \x -> let y = [1..30000000] in foldl' (+) 0 (y ++ [x])
GHC может заметить, что y
не зависит от x
, и переписать функцию на
f = let y = [1..30000000] in \x -> foldl' (+) 0 (y ++ [x])
В этом случае новая версия намного менее эффективна, поскольку ей придется считывать около 1 ГБ из памяти, в которой хранится y
, тогда как исходная версия будет работать в постоянном пространстве и помещаться в кэш процессора. Фактически, в GHC 6.12.1 функция f
почти в два раза быстрее при компиляции без оптимизаций, чем при компиляции с -O2
.