умноженный на x. Поэтому для представления рядов мы выберем конструкцию похожую на список:
data Ps
a = a :+: Ps aderiving
(Show, Eq)Но в нашем случае списки бесконечны, поэтому у нас лишь один конструктор. Далее мы будем писать
просто
Определим вспомогательные функции для создания рядов:
p0 :: Num
a => a -> Ps ap0 x =
x :+: p0 0ps :: Num
a => [a] -> Ps aps []
=
p0 0ps (a:
as) = a :+: ps asОбратите внимание на то, как мы дописываем бесконечный хвост нулей в конец ряда. Теперь давайте
определим функцию вычисления ряда. Мы будем вычислять лишь конечное число степеней.
eval :: Num
a => Int -> Ps a -> a -> aeval 0 _
_ =
0eval 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 ak .*
(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 fsfromInteger n =
p0 (fromInteger n)(.*
) :: Num a => a -> Ps a -> Ps ak .*
(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/gfromRational x =
p0 (fromRational x)Производная и интеграл
Производная одного члена ряда вычисляется так:
Из этого выражения по свойствам производной
(
мы можем получить формулу для всего ряда:
Для реализации нам понадобится вспомогательная функция, которая будет обновлять значение допол-
нительного множителя
diff :: Num
a => Ps a -> Ps adiff (f :+:
fs) = diff’ 1 fswhere
diff’ n (g :+: gs) = (n * g) :+: (diff’ (n+1) gs)Также мы можем вычислить и интеграл степенного ряда:
int :: Fractional
a => Ps a -> Ps aint (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 cosxcosx =
1 - int sinxИ это работает! Вычисление этих функций возможно за счёт того, что вне зависимости от аргумента
функция int вернёт ряд, у которого первый коэффициент равен нулю. Это значение подхватывается и ис-
пользуется на следующем шаге рекурсивных вычислений.
Через синус и косинус мы можем определить тангенс:
tanx =
sinx / cosxСтепенные ряды | 185
11.3 Водосборы