Может кто-нибудь объяснить это ленивое решение Фибоначчи?

Это код:

fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)

При оценке fibs представляет собой бесконечный список чисел Фибоначчи. Я не понимаю, как составлен список.

zipWith возвращает список, поэтому при сжатии fibs можно получить следующее:

0 : 1 : [1] : [1,2] : [1,2,3]

Потому что 0 : 1 : zipWith (+) [0,1] [1] дает [1], а zipWith (+) [0,1,1] [1,1] дает [1,2] и т. Д.).

Однако когда я запускаю код, я получаю правильный результат.

Что я здесь не понимаю?


person dopatraman    schedule 04.10.2015    source источник
comment
Почему это было отклонено?   -  person dopatraman    schedule 04.10.2015
comment
Откуда вы взяли 0 : 1 : [1] : [1,2] : [1,2,3]?   -  person dfeuer    schedule 04.10.2015
comment
Потому что 0 : 1 : zipWith (+) [0,1] [1] дает [1], а zipWith (+) [0,1,1] [1,1] дает [1,2] и т. Д.   -  person dopatraman    schedule 04.10.2015
comment
Это не заархивирование [0,1,1,...] с [1,1,...]. И это тоже не список промежуточных списков.   -  person dfeuer    schedule 04.10.2015
comment
Что он тогда делает   -  person dopatraman    schedule 04.10.2015


Ответы (1)


Ваше «Потому что» не раскрывает всей истории. Вы обрезаете списки на уровне «история на данный момент» и нетерпеливо оцениваете, а затем задаетесь вопросом, откуда взялось остальное. Это не совсем так, чтобы понять, что на самом деле происходит, так что хороший вопрос.

Что вычисляется, когда вы даете определение

fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)

? Очень мало. Вычисления начнутся, как только вы начнете использовать список. Ленивые вычисления выполняются только по запросу.

Какой спрос? Вы можете спросить: "Вы [] или x : xs?" и если это последнее, вы получите представление о деталях.

Когда мы задаем этот вопрос fibs, мы получаем, что

fibs = x0 : xs0
x0  = 0
xs0 = 1 : zipWith (+) fibs (drop 1 fibs)

но это означает (заменяя fibs, а затем x0)

xs0 = 1 : zipWith (+) (0 : xs0) (drop 1 (0 : xs0))

и когда мы снова спрашиваем, мы получаем, что

xs0 = x1 : xs1
x1  = 1
xs1 = zipWith (+) (0 : xs0) (drop 1 (0 : xs0))

so

xs1 = zipWith (+) (0 : 1 : xs1) (drop 1 (0 : 1 : xs1))

но теперь становится интересно, потому что нам нужно поработать. Достаточно работы, чтобы ответить на вопрос, не так ли? Когда мы смотрим на xs1, мы форсируем zipWith, который вынуждает drop.

xs1 = zipWith (+) (0 : 1 : xs1) (drop 1 (0 : 1 : xs1))
    = zipWith (+) (0 : 1 : xs1) (1 : xs1)
    = (0 + 1) : zipWith (+) (1 : xs1) xs1

so

xs1 = x2 : xs2
x2  = 0 + 1 = 1
xs2 = zipWith (+) (1 : xs1) xs1
    = zipWith (+) (1 : 1 : xs2) (1 : xs2)

Видеть? Мы утверждали, что нам известны первые два элемента одного заархивированного списка и первый элемент другого. Это означает, что мы сможем доставить следующий вывод и обновить наш «буфер». Когда мы смотрим на xs2, мы получаем

xs2 = zipWith (+) (1 : 1 : xs2) (1 : xs2)
    = (1 + 1) : zipWith (1 : xs2) xs2
xs2 = x3 : xs3
x3  = 1 + 1 = 2
xs3 = zipWith (1 : xs2) xs2
    = zipWith (1 : 2 : xs3) (2 : xs3)

и мы снова в порядке!

Каждый раз, когда нам требуется следующий элемент, мы также делаем шаг дальше от zipWith исчерпания элементов, что также хорошо, как раз в самый последний момент.

Ни одна из дисциплин, которая заставляет ценности проявляться в самый последний момент, не выражается в типах. На данный момент программисты должны убедиться, что хорошо типизированные программы не выйдут из строя из-за нехватки данных при поступлении запроса. (У меня есть планы что-то с этим сделать, но я постараюсь не отвлекаться.)

Ключ в том, что ленивое вычисление «по запросу» означает, что нам не нужно усекать списки до тех элементов, которые мы можем видеть при запуске процесса. Нам просто нужно знать, что мы всегда можем сделать следующий шаг.

person pigworker    schedule 04.10.2015
comment
Сегодня ты очень быстро справляешься с розыгрышем. - person dfeuer; 04.10.2015
comment
Я массово отвлекаюсь от того, что должен делать, но не хочу. - person pigworker; 04.10.2015
comment
Жалоба, выраженная в отступлении, довольно раздражает WRT реализацией zipWith в Data.Sequence, а также всего, что сделано с Traversable, которое полагается на два обхода одной и той же структуры, каждый раз попадая в одно и то же количество элементов. - person dfeuer; 04.10.2015