52700.fb2
∫ x
y( x) = 1 +
y( t) dt
0
Теперь переведём на Haskell:
expx = 1 + int expx
Кажется невероятным, но это и есть определение экспоненты. Так же мы можем определить и функции
для синуса и косинуса:
d sin x = cos x,
sin(0) = 0 ,
dx
d cos x = − sin x, cos(0) = 1
dx
Что приводит нас к:
sinx = int cosx
cosx = 1 - int sinx
И это работает! Вычисление этих функций возможно за счёт того, что вне зависимости от аргумента
функция int вернёт ряд, у которого первый коэффициент равен нулю. Это значение подхватывается и ис-
пользуется на следующем шаге рекурсивных вычислений.
Через синус и косинус мы можем определить тангенс:
tanx = sinx / cosx
Степенные ряды | 185
11.3 Водосборы
В этом примере мы рассмотрим одну интересную технику рекурсивных вычислений, которая называется
мемоизацией (memoization). Она заключается в том, что мы запоминаем все значения, с которыми вызывалась
функция и, если с данным значением функция уже вычислялась, просто используем значение из памяти, а
если значение ещё не вычислялось, вычисляем его и сохраняем.
В ленивых языках программирования для мемоизации функций часто используется такой приём. Мы со-
храняем все значения функции в некотором контейнере, а затем обращаемся к элементам. При этом значения
сохраняются в контейнере и не перевычисляются. Это происходит за счёт ленивых вычислений. Что интерес-
но вычисляются не все значения, а лишь те, которые нам действительно нужны, те которые мы извлекаем из
контейнера хотя бы один раз.
Посмотрим на такой классический пример. Вычисление чисел Фибоначчи. Каждое последующее число
ряда Фибоначчи равно сумме двух предыдущих. Наивное определение выглядит так:
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
В этом определении число вычислений растёт экспоненциально. Для того чтобы вычислить fib n нам
нужно вычислить fib (n-1) и fib (n-2), для того чтобы вычислить каждое из них нам нужно вычислить
ещё два числа, и так вычисления удваиваются на каждом шаге. Если мы вызовем в интерпретаторе fib 40,
то вычислитель зависнет. Что интересно в этой функции вычисления пересекаются, они могут быть пере-
использованы. Например для вычисления fib (n-1) и fib (n-2) нужно вычислить fib (n-2) (снова), fib
(n-3), fib (n-3) (снова) и fib (n-4).
Если мы сохраним все значения функции в списке, каждый вызов функции будет вычислен лишь один
раз:
fib’ :: Int -> Int
fib’ n = fibs !! n
where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)