Я немного объясню, как это работает внутри. Во-первых, вы должны понимать, что Haskell использует для своих значений вещь, называемую thunk . Thunk - это, по сути, значение, которое еще не было вычислено - его следует рассматривать как функцию от 0 аргументов. Всякий раз, когда Haskell хочет, он может оценить (или частично оценить) thunk, превратив его в реальную ценность. Если это только частично оценивает thunk, то полученное значение будет иметь больше thunk в нем.
Например, рассмотрим выражение:
(2 + 3, 4)
На обычном языке это значение будет храниться в памяти как (5, 4)
, но в Haskell оно хранится как (<thunk 2 + 3>, 4)
. Если вы попросите второй элемент этого кортежа, он скажет вам «4», не добавляя 2 и 3 вместе. Только если вы попросите первый элемент этого кортежа, он оценит thunk и поймет, что это 5.
С фибсами это немного сложнее, потому что это рекурсивно, но мы можем использовать ту же идею. Поскольку fibs
не принимает аргументов, Haskell будет постоянно хранить любые элементы списка, которые были обнаружены - это важно.
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Это помогает визуализировать текущее знание Хаскеллом трех выражений: fibs
, tail fibs
и zipWith (+) fibs (tail fibs)
. Предположим, что Haskell начинает, зная следующее:
fibs = 0 : 1 : <thunk1>
tail fibs = 1 : <thunk1>
zipWith (+) fibs (tail fibs) = <thunk1>
Обратите внимание, что 2-я строка - это только первая, сдвинутая влево, а 3-я строка - первые две строки, суммированные.
Попросите take 2 fibs
, и вы получите [0, 1]
. Хаскелу не нужно дополнительно оценивать вышесказанное, чтобы это выяснить.
Попросите take 3 fibs
, и Хаскелл получит 0 и 1, а затем поймет, что ему нужно частично оценить ствол. Чтобы полностью оценить zipWith (+) fibs (tail fibs)
, ему необходимо сложить первые две строки - он не может сделать это полностью, но может начать для суммирования первых двух строк:
fibs = 0 : 1 : 1: <thunk2>
tail fibs = 1 : 1 : <thunk2>
zipWith (+) fibs (tail fibs) = 1 : <thunk2>
Обратите внимание, что я заполнил «1» в 3-й строке, и она автоматически появилась и в первой и во второй строке, поскольку все три строки имеют один и тот же блок (представьте себе, что указатель записан в ). И поскольку он не закончил оценку, он создал новый thunk, содержащий rest списка, если это когда-либо понадобится.
Это не нужно, потому что take 3 fibs
сделано: [0, 1, 1]
. Но теперь, скажем, вы просите take 50 fibs
; Haskell уже помнит 0, 1 и 1. Но он должен продолжать идти. Итак, продолжается суммирование первых двух строк:
fibs = 0 : 1 : 1 : 2 : <thunk3>
tail fibs = 1 : 1 : 2 : <thunk3>
zipWith (+) fibs (tail fibs) = 1 : 2 : <thunk3>
...
fibs = 0 : 1 : 1 : 2 : 3 : <thunk4>
tail fibs = 1 : 1 : 2 : 3 : <thunk4>
zipWith (+) fibs (tail fibs) = 1 : 2 : 3 : <thunk4>
И так далее, пока он не заполнит 48 столбцов 3-го ряда и, таким образом, не сработает первые 50 чисел. Haskell оценивает столько, сколько ему нужно, и оставляет бесконечный «остаток» последовательности в виде объекта thunk на тот случай, если он когда-либо понадобится.
Обратите внимание, что если вы впоследствии запросите take 25 fibs
, Haskell больше не будет его оценивать - он просто возьмет первые 25 чисел из списка, который он уже рассчитал.
Редактировать : Добавлен уникальный номер для каждого устройства, чтобы избежать путаницы.