GHC не запоминает функции.
Однако он вычисляет любое заданное выражение в коде не чаще одного раза за раз, когда вводится окружающее его лямбда-выражение, или не более одного раза, если оно находится на верхнем уровне. Определение того, где находятся лямбда-выражения, может быть немного сложным, если вы используете синтаксический сахар, как в вашем примере, поэтому давайте преобразуем их в эквивалентный синтаксис без сахара:
m1' = (!!) (filter odd [1..]) -- NB: See below!
m2' = \n -> (!!) (filter odd [1..]) n
(Примечание: отчет Haskell 98 на самом деле описывает левую секцию оператора (a %)
как эквивалент \b -> (%) a b
, но GHC обессахаривает ее (%) a
. Технически они отличаются, потому что их можно различить по seq
. Думаю, я мог бы подать заявку на GHC Trac по этому поводу.)
Учитывая это, вы можете видеть, что in m1'
, выражение filter odd [1..]
не содержится ни в одном лямбда-выражении, поэтому оно будет вычисляться только один раз за запуск вашей программы, в то время как in 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
.
seq
m1 10000000). Однако есть разница, когда не указан флаг оптимизации. И оба варианта вашего "f" имеют максимальное размещение 5356 байт независимо от оптимизации, кстати (с меньшим общим распределением при использовании -O2).f
:main = interact $ unlines . (show . map f . read) . lines
; компилировать с или без-O2
; тогдаecho 1 | ./main
. Если вы напишете тест вродеmain = print (f 5)
, тогдаy
можно будет собирать мусор по мере его использования, и нет никакой разницы между двумяf
s.map (show . f . read)
, конечно. И теперь, когда я загрузил GHC 6.12.3, я вижу те же результаты, что и в GHC 6.12.1. И да, вы правы насчет оригиналаm1
иm2
: версии GHC, которые выполняют такой подъем с включенной оптимизацией, будут преобразованыm2
вm1
.m1 вычисляется только один раз, потому что это постоянная заявочная форма, в то время как m2 не является CAF, и поэтому вычисляется для каждой оценки.
См. Вики GHC по CAF: http://www.haskell.org/haskellwiki/Constant_applicative_form
источник
[1 ..]
, вычисляется ли список только один раз во время выполнения программы или один раз для каждого приложения функции, но связано ли это с CAF?m1
это CAF, второй применяется иfilter odd [1..]
(не только[1..]
!) Вычисляется только один раз. GHC может также отметить, что этоm2
относится кfilter odd [1..]
тому же преобразователю, который используется, и разместить ссылку на негоm1
, но это будет плохой идеей: в некоторых ситуациях это может привести к большим утечкам памяти.[1..]
иfilter odd [1..]
. В остальном я все еще не убежден. Если я не ошибаюсь, CAF имеет значение только тогда, когда мы хотим утверждать, что компилятор может заменитьfilter odd [1..]
inm2
глобальным преобразователем (который может быть даже тем же преобразователем, что и используемый вm1
). Но в ситуации, когда задает вопрос , компилятор не сделал эту «оптимизацию», и я не вижу ее отношения к вопросу.m1
, и это делает.Между двумя формами есть принципиальное различие: ограничение мономорфизма применяется к m1, но не к m2, потому что m2 явно указал аргументы. Итак, тип m2 является общим, а тип m1 - специфическим. Им присваиваются следующие типы:
Большинство компиляторов и интерпретаторов Haskell (все, что я знаю на самом деле) не запоминают полиморфные структуры, поэтому внутренний список m2 воссоздается каждый раз, когда он вызывается, а m1 - нет.
источник
Я не уверен, потому что сам новичок в Haskell, но похоже, что это потому, что вторая функция параметризована, а первая - нет. Природа функции такова, что ее результат зависит от входного значения, а в функциональной парадигме, особенно, зависит ТОЛЬКО от входа. Очевидно, подразумевается, что функция без параметров всегда возвращает одно и то же значение снова и снова, несмотря ни на что.
По сути, в компиляторе GHC есть механизм оптимизации, который использует этот факт для вычисления значения такой функции только один раз за все время выполнения программы. Конечно, делает это лениво, но, тем не менее, делает. Я сам это заметил, когда писал такую функцию:
Затем , чтобы проверить это, я вошел в GHCI и написал:
primes !! 1000
. Прошло несколько секунд, но в конце концов я получил ответ:7927
. Тогда я позвонилprimes !! 1001
и сразу получил ответ. Точно так же в одно мгновение я получил результатtake 1000 primes
, потому что Haskell должен был вычислить весь список из тысячи элементов, чтобы вернуть 1001-й элемент раньше.Таким образом, если вы можете написать свою функцию так, чтобы она не принимала параметров, вы, вероятно, захотите этого. ;)
источник