Стиль без точек невелик

У меня есть следующий, часто цитируемый код для вычисления n-го числа Фибоначчи в Haskell:

fibonacci :: Int -> Integer
fibonacci = (map fib [0..] !!)
    where fib 0 = 0
          fib 1 = 1
          fib n = fibonacci (n-2) + fibonacci (n-1)  

Используя это, я могу делать такие вызовы, как:

ghci> fibonacci 1000

и получите почти мгновенный ответ.

Однако, если я изменю приведенный выше код так, чтобы он не был в свободном стиле, т.е.

fibonacci :: Int -> Integer
fibonacci x = (map fib [0..] !!) x
    where fib 0 = 0
          fib 1 = 1
          fib n = fibonacci (n-2) + fibonacci (n-1) 

он значительно медленнее. В той мере, в какой вызов, например

ghci> fibonacci 1000

зависаний.

Мое понимание заключалось в том, что эти две части кода были эквивалентны, но GHCi требует отличия. Кто-нибудь объясняет это поведение?

Ответ 1

Чтобы заметить разницу, вам следует, вероятно, взглянуть на Core. Я предполагаю, что это сводится к сравнению (примерно)

let f = map fib [0..] in \x -> f !! x

к

\x -> let f = map fib [0..] in f !! x

Последний будет компрометировать f с нуля при каждом вызове. Первое не позволяет эффективно кэшировать один и тот же f для каждого вызова.

Бывает, что в этом конкретном случае GHC смог оптимизировать второй в первом, как только оптимизация включена.

Обратите внимание, однако, что GHC не всегда выполняет это преобразование, поскольку это не всегда оптимизация. Кэш, используемый первым, хранится в памяти навсегда. Это может привести к пустой памяти в зависимости от выполняемой функции.

Ответ 2

Я попытался найти его, но вычеркнул. Я думаю, что у меня есть это на моем ПК дома. Я читал, что функции, использующие фиксированную точку, были изначально быстрее. Существуют и другие причины использования фиксированной точки. Я столкнулся с этим, написав эту итеративную функцию Фибоначчи. Я хотел посмотреть, как будет выполняться итеративная версия, тогда я понял, что у меня нет готового способа измерения. Я неофит Хаскелла. Но вот итеративная версия для кого-то для тестирования. Я не мог определить это, если бы не использовал точку после первой последней функции. Я не мог его уменьшить. параметр [0,1] является фиксированным и не должен указываться в качестве значения параметра.

Prelude> fib = last . flip take (iterate (\ls -> ls ++ [last ls + last (init ls)]) [0,1])
Prelude> fib 25

[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025]