Lazy crazy
Aug. 14th, 2010 01:33 amПоговорим о Хаскеле... Первая вещь, которая отличает_от и поражает воображение, это до невозможности ленивые вычисления. Хаскел никогда ничего не вычисляет, пока оно не понадобится.
Например, можно сказать [1..], и Хаскел бросится печатать бесконечный список чисел. Но если попросить take 5 [1..], то ответом будет [1,2,3,4,5], то есть будут вычислены только те элементы, которые нужны. Не впечатляет? Идём дальше.
Классическая задача — числа Фибоначчи. Классическая же рекурсивная реализация, понятная без каких-либо тайных знаний_о:
fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2)
Говорим fib 10, получаем 55, всё честно.
А если хочется список чисел? Первая мысль императивного программиста: какой длины объявлять массив? Вот в этом месте требуется смена парадигмы. В Хаскеле нет проблем (почти) работать с бесконечными списками.
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Говорим take 11 fibs, получаем [0,1,1,2,3,5,8,13,21,34,55]. Говорим fibs!!10, получаем 55.
Тут уже требуется пара пояснений. Функция tail возвращает весь список, кроме головы: tail [1,2,3] даёт [2,3]. Функция zipWith «сшивает» два списка с помощью функции-параметра, например zipWith (+) [10,20,30] [1,2,3] даст [11,22,33], а zipWith (-) [10,20,30] [1,2,3] даст [9,18,27].
То есть вот что происходит. Сначала в fibs нам известны первые два элемента. А затем происходит рекурсивное (потенциально бесконечное!) сложение списка самого с собой, сдвинутым на одну позицию влево, как-то так:
[0,1,… + [1,… = [1,…
[0,1,1,… + [1,1,… = [1,2,…
[0,1,1,2,… + [1,1,2,… = [1,2,3,…
Ну и так далее, алгоритм O(n) — понятно, не самый быстрый, но для упражнения сойдёт. Суть же в том, что благодаря ленивости вычислений нам не надо заботиться о максимальном размере списка: сколько понадобится, столько и будет, но не больше! По-моему, красиво.