умноженный на x. Поэтому для представления рядов мы выберем конструкцию похожую на список:
data Ps a = a :+: Ps a
deriving (Show, Eq)
Но в нашем случае списки бесконечны, поэтому у нас лишь один конструктор. Далее мы будем писать
просто
Определим вспомогательные функции для создания рядов:
p0 :: Num a => a -> Ps a
p0 x = x :+: p0 0
ps :: Num a => [a] -> Ps a
ps []
= p0 0
ps (a:as) = a :+: ps as
Обратите внимание на то, как мы дописываем бесконечный хвост нулей в конец ряда. Теперь давайте
определим функцию вычисления ряда. Мы будем вычислять лишь конечное число степеней.
eval :: Num a => Int -> Ps a -> a -> a
eval 0 _
_ = 0
eval n (a :+: p) x = a + x * eval (n-1) p x
В первом случае мы хотим вычислить ноль степеней ряда, поэтому мы возвращаем ноль, а во втором
случае значение ряда
Степенные ряды | 183
Арифметика рядов
В результате сложения и умножения рядов также получается ряд. Также мы можем создать ряд из числа.
Эти операции говорят о том, что мы можем сделать степенной ряд экземпляром класса Num.
Сложение
Рекурсивное представление ряда
хотим определить. Теперь у нас нет бесконечного набора коэффициентов, у нас всего лишь одно число и ещё
один ряд. Операции существенно упрощаются. Так сложение двух бесконечных рядов имеет вид:
Переведём на Haskell:
(f :+: fs) + (g :+: gs) = (f + g) :+: (fs + gs)
Умножение
Умножим два ряда:
Переведём:
(.*) :: Num a => a -> Ps a -> Ps a
k .* (f :+: fs) = (k * f) :+: (k .* fs)
(f :+: fs) * (g :+: gs) = (f * g) :+: (f .* gs + fs * (g :+: gs))
Дополнительная операция (.*) выполняет умножение всех коэффициентов ряда на число.
Класс Num
Соберём определения для методов класса Num вместе:
instance Num a => Num (Ps a) where
(f :+: fs) + (g :+: gs) = (f + g) :+: (fs + gs)
(f :+: fs) * (g :+: gs) = (f * g) :+: (f .* gs + fs * (g :+: gs))
negate (f :+: fs) = negate f :+: negate fs
fromInteger n = p0 (fromInteger n)
(.*) :: Num a => a -> Ps a -> Ps a
k .* (f :+: fs) = (k * f) :+: (k .* fs)
Методы abs и signum не определены для рядов. Обратите внимание на то, как рекурсивное определение
рядов приводит к рекурсивным определениям функций для рядов. Этот приём очень характерен для Haskell.
Поскольку наш ряд это число и ещё один ряд за счёт рекурсии мы можем воспользоваться операцией, которую
мы определяем, на “хвостовом” ряде.
Деление
Результат деления
Переписав
=
Следовательно
=
Если
184 | Глава 11: Ленивые чудеса
class Fractional a => Fractional (Ps a) where
(0 :+: fs) / (0 :+: gs) = fs / gs
(f :+: fs) / (g :+: gs) = q :+: ((fs - q .* gs)/(g :+: gs))
where q = f/g
fromRational x = p0 (fromRational x)
Производная и интеграл
Производная одного члена ряда вычисляется так:
Из этого выражения по свойствам производной
(
мы можем получить формулу для всего ряда:
Для реализации нам понадобится вспомогательная функция, которая будет обновлять значение допол-
нительного множителя
diff :: Num a => Ps a -> Ps a
diff (f :+: fs) = diff’ 1 fs
where diff’ n (g :+: gs) = (n * g) :+: (diff’ (n+1) gs)
Также мы можем вычислить и интеграл степенного ряда:
int :: Fractional a => Ps a -> Ps a
int (f :+: fs) = 0 :+: (int’ 1 fs)
where int’ n (g :+: gs) = (g / n) :+: (int’ (n+1) gs)
Элементарные функции
Мы можем выразить элементарные функции через операции взятия производной и интегрирования. К
примеру уравнение для
Проинтегрируем с начальным условием
∫
0
Теперь переведём на Haskell:
expx = 1 + int expx
Кажется невероятным, но это и есть определение экспоненты. Так же мы можем определить и функции
для синуса и косинуса:
sin(0) = 0
Что приводит нас к:
sinx = int cosx
cosx = 1 - int sinx
И это работает! Вычисление этих функций возможно за счёт того, что вне зависимости от аргумента
функция int вернёт ряд, у которого первый коэффициент равен нулю. Это значение подхватывается и ис-
пользуется на следующем шаге рекурсивных вычислений.
Через синус и косинус мы можем определить тангенс:
tanx = sinx / cosx
Степенные ряды | 185
11.3 Водосборы